forked from potsda.mn/mobilizon
Merge branch 'save-remote-pics' into 'master'
Save remote pics See merge request framasoft/mobilizon!763
This commit is contained in:
commit
6833d79611
|
@ -20,6 +20,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
* Docker
|
* Docker
|
||||||
`docker-compose exec mobilizon mobilizon_ctl maintenance.fix_unattached_media_in_body`
|
`docker-compose exec mobilizon mobilizon_ctl maintenance.fix_unattached_media_in_body`
|
||||||
|
|
||||||
|
* **Refresh remote profiles to save avatars locally**
|
||||||
|
Profile avatars and banners were previously only proxified and cached. Now we save them locally. Refreshing all remote actors will save profile media locally instead.
|
||||||
|
|
||||||
|
* Source install
|
||||||
|
`MIX_ENV=prod mix mobilizon.actors.refresh --all`
|
||||||
|
* Docker
|
||||||
|
`docker-compose exec mobilizon mobilizon_ctl actors.refresh --all`
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- **Add a command to clean orphan media files**. There's a `--dry-run` option to see what files would have been deleted.
|
- **Add a command to clean orphan media files**. There's a `--dry-run` option to see what files would have been deleted.
|
||||||
|
|
|
@ -81,17 +81,6 @@ config :mobilizon, Mobilizon.Web.Upload,
|
||||||
|
|
||||||
config :mobilizon, Mobilizon.Web.Upload.Uploader.Local, uploads: "uploads"
|
config :mobilizon, Mobilizon.Web.Upload.Uploader.Local, uploads: "uploads"
|
||||||
|
|
||||||
config :mobilizon, :media_proxy,
|
|
||||||
enabled: true,
|
|
||||||
proxy_opts: [
|
|
||||||
redirect_on_failure: false,
|
|
||||||
max_body_length: 25 * 1_048_576,
|
|
||||||
http: [
|
|
||||||
follow_redirect: true,
|
|
||||||
pool: :media
|
|
||||||
]
|
|
||||||
]
|
|
||||||
|
|
||||||
config :mobilizon, Mobilizon.Web.Email.Mailer,
|
config :mobilizon, Mobilizon.Web.Email.Mailer,
|
||||||
adapter: Bamboo.SMTPAdapter,
|
adapter: Bamboo.SMTPAdapter,
|
||||||
server: "localhost",
|
server: "localhost",
|
||||||
|
|
|
@ -109,10 +109,14 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Prop } from "vue-property-decorator";
|
import { Component, Prop, Watch } from "vue-property-decorator";
|
||||||
import { mixins } from "vue-class-component";
|
import { mixins } from "vue-class-component";
|
||||||
import { FETCH_GROUP } from "@/graphql/group";
|
import { FETCH_GROUP } from "@/graphql/group";
|
||||||
import { buildFileFromIMedia, readFileAsync } from "@/utils/image";
|
import {
|
||||||
|
buildFileFromIMedia,
|
||||||
|
buildFileVariable,
|
||||||
|
readFileAsync,
|
||||||
|
} from "@/utils/image";
|
||||||
import GroupMixin from "@/mixins/group";
|
import GroupMixin from "@/mixins/group";
|
||||||
import { PostVisibility } from "@/types/enums";
|
import { PostVisibility } from "@/types/enums";
|
||||||
import { TAGS } from "../../graphql/tags";
|
import { TAGS } from "../../graphql/tags";
|
||||||
|
@ -206,6 +210,13 @@ export default class EditPost extends mixins(GroupMixin) {
|
||||||
this.pictureFile = await buildFileFromIMedia(this.post.picture);
|
this.pictureFile = await buildFileFromIMedia(this.post.picture);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Watch("post")
|
||||||
|
async updatePostPicture(oldPost: IPost, newPost: IPost): Promise<void> {
|
||||||
|
if (oldPost.picture !== newPost.picture) {
|
||||||
|
this.pictureFile = await buildFileFromIMedia(this.post.picture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line consistent-return
|
// eslint-disable-next-line consistent-return
|
||||||
async publish(draft: boolean): Promise<void> {
|
async publish(draft: boolean): Promise<void> {
|
||||||
this.errors = {};
|
this.errors = {};
|
||||||
|
@ -292,19 +303,11 @@ export default class EditPost extends mixins(GroupMixin) {
|
||||||
async buildPicture(): Promise<Record<string, unknown>> {
|
async buildPicture(): Promise<Record<string, unknown>> {
|
||||||
let obj: { picture?: any } = {};
|
let obj: { picture?: any } = {};
|
||||||
if (this.pictureFile) {
|
if (this.pictureFile) {
|
||||||
const pictureObj = {
|
const pictureObj = buildFileVariable(this.pictureFile, "picture");
|
||||||
picture: {
|
obj = { ...obj, ...pictureObj };
|
||||||
picture: {
|
|
||||||
name: this.pictureFile.name,
|
|
||||||
alt: `${this.actualGroup.preferredUsername}'s avatar`,
|
|
||||||
file: this.pictureFile,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
obj = { ...pictureObj };
|
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if (this.post.picture) {
|
if (this.post.picture && this.pictureFile) {
|
||||||
const oldPictureFile = (await buildFileFromIMedia(
|
const oldPictureFile = (await buildFileFromIMedia(
|
||||||
this.post.picture
|
this.post.picture
|
||||||
)) as File;
|
)) as File;
|
||||||
|
@ -333,6 +336,7 @@ export default class EditPost extends mixins(GroupMixin) {
|
||||||
const roles = Array.isArray(givenRole) ? givenRole : [givenRole];
|
const roles = Array.isArray(givenRole) ? givenRole : [givenRole];
|
||||||
return (
|
return (
|
||||||
this.person &&
|
this.person &&
|
||||||
|
this.actualGroup &&
|
||||||
this.person.memberships.elements.some(
|
this.person.memberships.elements.some(
|
||||||
({ parent: { id }, role }) =>
|
({ parent: { id }, role }) =>
|
||||||
id === this.actualGroup.id && roles.includes(role)
|
id === this.actualGroup.id && roles.includes(role)
|
||||||
|
|
|
@ -766,7 +766,10 @@ defmodule Mobilizon.Federation.ActivityPub do
|
||||||
|
|
||||||
res =
|
res =
|
||||||
with {:ok, %{status: 200, body: body}} <-
|
with {:ok, %{status: 200, body: body}} <-
|
||||||
Tesla.get(url, headers: [{"Accept", "application/activity+json"}]),
|
Tesla.get(url,
|
||||||
|
headers: [{"Accept", "application/activity+json"}],
|
||||||
|
follow_redirect: true
|
||||||
|
),
|
||||||
:ok <- Logger.debug("response okay, now decoding json"),
|
:ok <- Logger.debug("response okay, now decoding json"),
|
||||||
{:ok, data} <- Jason.decode(body) do
|
{:ok, data} <- Jason.decode(body) do
|
||||||
Logger.debug("Got activity+json response at actor's endpoint, now converting data")
|
Logger.debug("Got activity+json response at actor's endpoint, now converting data")
|
||||||
|
|
|
@ -382,7 +382,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
|
||||||
{:ok, activity, new_actor}
|
{:ok, activity, new_actor}
|
||||||
else
|
else
|
||||||
e ->
|
e ->
|
||||||
Logger.debug(inspect(e))
|
Logger.error(inspect(e))
|
||||||
:error
|
:error
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -11,7 +11,9 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Actor do
|
||||||
alias Mobilizon.Federation.ActivityPub.Utils
|
alias Mobilizon.Federation.ActivityPub.Utils
|
||||||
alias Mobilizon.Federation.ActivityStream.{Converter, Convertible}
|
alias Mobilizon.Federation.ActivityStream.{Converter, Convertible}
|
||||||
|
|
||||||
alias Mobilizon.Web.MediaProxy
|
alias Mobilizon.Service.HTTP.RemoteMediaDownloaderClient
|
||||||
|
alias Mobilizon.Service.RichMedia.Parser
|
||||||
|
alias Mobilizon.Web.Upload
|
||||||
|
|
||||||
@behaviour Converter
|
@behaviour Converter
|
||||||
|
|
||||||
|
@ -30,18 +32,10 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Actor do
|
||||||
@spec as_to_model_data(map()) :: {:ok, map()}
|
@spec as_to_model_data(map()) :: {:ok, map()}
|
||||||
def as_to_model_data(%{"type" => type} = data) when type in @allowed_types do
|
def as_to_model_data(%{"type" => type} = data) when type in @allowed_types do
|
||||||
avatar =
|
avatar =
|
||||||
data["icon"]["url"] &&
|
download_picture(get_in(data, ["icon", "url"]), get_in(data, ["icon", "name"]), "avatar")
|
||||||
%{
|
|
||||||
"name" => data["icon"]["name"] || "avatar",
|
|
||||||
"url" => MediaProxy.url(data["icon"]["url"])
|
|
||||||
}
|
|
||||||
|
|
||||||
banner =
|
banner =
|
||||||
data["image"]["url"] &&
|
download_picture(get_in(data, ["image", "url"]), get_in(data, ["image", "name"]), "banner")
|
||||||
%{
|
|
||||||
"name" => data["image"]["name"] || "banner",
|
|
||||||
"url" => MediaProxy.url(data["image"]["url"])
|
|
||||||
}
|
|
||||||
|
|
||||||
%{
|
%{
|
||||||
url: data["id"],
|
url: data["id"],
|
||||||
|
@ -140,4 +134,16 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Actor do
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec download_picture(String.t() | nil, String.t(), String.t()) :: map()
|
||||||
|
defp download_picture(nil, _name, _default_name), do: nil
|
||||||
|
|
||||||
|
defp download_picture(url, name, default_name) do
|
||||||
|
with {:ok, %{body: body, status: code, headers: response_headers}}
|
||||||
|
when code in 200..299 <- RemoteMediaDownloaderClient.get(url),
|
||||||
|
name <- name || Parser.get_filename_from_response(response_headers, url) || default_name,
|
||||||
|
{:ok, file} <- Upload.store(%{body: body, name: name}) do
|
||||||
|
file
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,7 +10,6 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Event do
|
||||||
alias Mobilizon.Addresses
|
alias Mobilizon.Addresses
|
||||||
alias Mobilizon.Addresses.Address
|
alias Mobilizon.Addresses.Address
|
||||||
alias Mobilizon.Events.Event, as: EventModel
|
alias Mobilizon.Events.Event, as: EventModel
|
||||||
alias Mobilizon.Medias.Media
|
|
||||||
|
|
||||||
alias Mobilizon.Federation.ActivityStream.{Converter, Convertible}
|
alias Mobilizon.Federation.ActivityStream.{Converter, Convertible}
|
||||||
alias Mobilizon.Federation.ActivityStream.Converter.Address, as: AddressConverter
|
alias Mobilizon.Federation.ActivityStream.Converter.Address, as: AddressConverter
|
||||||
|
@ -21,7 +20,8 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Event do
|
||||||
fetch_tags: 1,
|
fetch_tags: 1,
|
||||||
fetch_mentions: 1,
|
fetch_mentions: 1,
|
||||||
build_tags: 1,
|
build_tags: 1,
|
||||||
maybe_fetch_actor_and_attributed_to_id: 1
|
maybe_fetch_actor_and_attributed_to_id: 1,
|
||||||
|
process_pictures: 2
|
||||||
]
|
]
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
@ -34,6 +34,9 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Event do
|
||||||
defdelegate model_to_as(event), to: EventConverter
|
defdelegate model_to_as(event), to: EventConverter
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@online_address_name "Website"
|
||||||
|
@banner_picture_name "Banner"
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Converts an AP object data to our internal data structure.
|
Converts an AP object data to our internal data structure.
|
||||||
"""
|
"""
|
||||||
|
@ -47,30 +50,16 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Event do
|
||||||
{:tags, tags} <- {:tags, fetch_tags(object["tag"])},
|
{:tags, tags} <- {:tags, fetch_tags(object["tag"])},
|
||||||
{:mentions, mentions} <- {:mentions, fetch_mentions(object["tag"])},
|
{:mentions, mentions} <- {:mentions, fetch_mentions(object["tag"])},
|
||||||
{:visibility, visibility} <- {:visibility, get_visibility(object)},
|
{:visibility, visibility} <- {:visibility, get_visibility(object)},
|
||||||
{:options, options} <- {:options, get_options(object)} do
|
{:options, options} <- {:options, get_options(object)},
|
||||||
attachments =
|
[description: description, picture_id: picture_id, medias: medias] <-
|
||||||
object
|
process_pictures(object, actor_id) do
|
||||||
|> Map.get("attachment", [])
|
|
||||||
|> Enum.filter(fn attachment -> Map.get(attachment, "type", "Document") == "Document" end)
|
|
||||||
|
|
||||||
picture_id =
|
|
||||||
with true <- length(attachments) > 0,
|
|
||||||
{:ok, %Media{id: picture_id}} <-
|
|
||||||
attachments
|
|
||||||
|> hd()
|
|
||||||
|> MediaConverter.find_or_create_media(actor_id) do
|
|
||||||
picture_id
|
|
||||||
else
|
|
||||||
_err ->
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
|
|
||||||
%{
|
%{
|
||||||
title: object["name"],
|
title: object["name"],
|
||||||
description: object["content"],
|
description: description,
|
||||||
organizer_actor_id: actor_id,
|
organizer_actor_id: actor_id,
|
||||||
attributed_to_id: if(is_nil(attributed_to), do: nil, else: attributed_to.id),
|
attributed_to_id: if(is_nil(attributed_to), do: nil, else: attributed_to.id),
|
||||||
picture_id: picture_id,
|
picture_id: picture_id,
|
||||||
|
medias: medias,
|
||||||
begins_on: object["startTime"],
|
begins_on: object["startTime"],
|
||||||
ends_on: object["endTime"],
|
ends_on: object["endTime"],
|
||||||
category: object["category"],
|
category: object["category"],
|
||||||
|
@ -143,6 +132,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Event do
|
||||||
|> maybe_add_physical_address(event)
|
|> maybe_add_physical_address(event)
|
||||||
|> maybe_add_event_picture(event)
|
|> maybe_add_event_picture(event)
|
||||||
|> maybe_add_online_address(event)
|
|> maybe_add_online_address(event)
|
||||||
|
|> maybe_add_inline_media(event)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Get only elements that we have in EventOptions
|
# Get only elements that we have in EventOptions
|
||||||
|
@ -213,7 +203,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Event do
|
||||||
"type" => "Link",
|
"type" => "Link",
|
||||||
"href" => url,
|
"href" => url,
|
||||||
"mediaType" => "text/html",
|
"mediaType" => "text/html",
|
||||||
"name" => "Website"
|
"name" => @online_address_name
|
||||||
} ->
|
} ->
|
||||||
url
|
url
|
||||||
|
|
||||||
|
@ -239,7 +229,12 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Event do
|
||||||
res,
|
res,
|
||||||
"attachment",
|
"attachment",
|
||||||
[],
|
[],
|
||||||
&(&1 ++ [MediaConverter.model_to_as(event.picture)])
|
&(&1 ++
|
||||||
|
[
|
||||||
|
event.picture
|
||||||
|
|> MediaConverter.model_to_as()
|
||||||
|
|> Map.put("name", @banner_picture_name)
|
||||||
|
])
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -258,9 +253,21 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Event do
|
||||||
"type" => "Link",
|
"type" => "Link",
|
||||||
"href" => event.online_address,
|
"href" => event.online_address,
|
||||||
"mediaType" => "text/html",
|
"mediaType" => "text/html",
|
||||||
"name" => "Website"
|
"name" => @online_address_name
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec maybe_add_inline_media(map(), Event.t()) :: map()
|
||||||
|
defp maybe_add_inline_media(res, event) do
|
||||||
|
medias = Enum.map(event.media, &MediaConverter.model_to_as/1)
|
||||||
|
|
||||||
|
Map.update(
|
||||||
|
res,
|
||||||
|
"attachment",
|
||||||
|
[],
|
||||||
|
&(&1 ++ medias)
|
||||||
|
)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -9,9 +9,15 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Post do
|
||||||
alias Mobilizon.Federation.ActivityPub
|
alias Mobilizon.Federation.ActivityPub
|
||||||
alias Mobilizon.Federation.ActivityPub.Utils
|
alias Mobilizon.Federation.ActivityPub.Utils
|
||||||
alias Mobilizon.Federation.ActivityStream.{Converter, Convertible}
|
alias Mobilizon.Federation.ActivityStream.{Converter, Convertible}
|
||||||
|
alias Mobilizon.Federation.ActivityStream.Converter.Media, as: MediaConverter
|
||||||
alias Mobilizon.Posts.Post
|
alias Mobilizon.Posts.Post
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
|
import Mobilizon.Federation.ActivityStream.Converter.Utils,
|
||||||
|
only: [
|
||||||
|
process_pictures: 2
|
||||||
|
]
|
||||||
|
|
||||||
@behaviour Converter
|
@behaviour Converter
|
||||||
|
|
||||||
defimpl Convertible, for: Post do
|
defimpl Convertible, for: Post do
|
||||||
|
@ -20,6 +26,8 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Post do
|
||||||
defdelegate model_to_as(post), to: PostConverter
|
defdelegate model_to_as(post), to: PostConverter
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@banner_picture_name "Banner"
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Convert an post struct to an ActivityStream representation
|
Convert an post struct to an ActivityStream representation
|
||||||
"""
|
"""
|
||||||
|
@ -35,8 +43,11 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Post do
|
||||||
"name" => post.title,
|
"name" => post.title,
|
||||||
"content" => post.body,
|
"content" => post.body,
|
||||||
"attributedTo" => creator_url,
|
"attributedTo" => creator_url,
|
||||||
"published" => (post.publish_at || post.inserted_at) |> to_date()
|
"published" => (post.publish_at || post.inserted_at) |> to_date(),
|
||||||
|
"attachment" => []
|
||||||
}
|
}
|
||||||
|
|> maybe_add_post_picture(post)
|
||||||
|
|> maybe_add_inline_media(post)
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
@ -48,15 +59,19 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Post do
|
||||||
%{"type" => "Article", "actor" => creator, "attributedTo" => group} = object
|
%{"type" => "Article", "actor" => creator, "attributedTo" => group} = object
|
||||||
) do
|
) do
|
||||||
with {:ok, %Actor{id: attributed_to_id}} <- get_actor(group),
|
with {:ok, %Actor{id: attributed_to_id}} <- get_actor(group),
|
||||||
{:ok, %Actor{id: author_id}} <- get_actor(creator) do
|
{:ok, %Actor{id: author_id}} <- get_actor(creator),
|
||||||
|
[description: description, picture_id: picture_id, medias: medias] <-
|
||||||
|
process_pictures(object, attributed_to_id) do
|
||||||
%{
|
%{
|
||||||
title: object["name"],
|
title: object["name"],
|
||||||
body: object["content"],
|
body: description,
|
||||||
url: object["id"],
|
url: object["id"],
|
||||||
attributed_to_id: attributed_to_id,
|
attributed_to_id: attributed_to_id,
|
||||||
author_id: author_id,
|
author_id: author_id,
|
||||||
local: false,
|
local: false,
|
||||||
publish_at: object["published"]
|
publish_at: object["published"],
|
||||||
|
picture_id: picture_id,
|
||||||
|
medias: medias
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{:error, err} -> {:error, err}
|
{:error, err} -> {:error, err}
|
||||||
|
@ -70,4 +85,34 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Post do
|
||||||
|
|
||||||
defp to_date(%DateTime{} = date), do: DateTime.to_iso8601(date)
|
defp to_date(%DateTime{} = date), do: DateTime.to_iso8601(date)
|
||||||
defp to_date(%NaiveDateTime{} = date), do: NaiveDateTime.to_iso8601(date)
|
defp to_date(%NaiveDateTime{} = date), do: NaiveDateTime.to_iso8601(date)
|
||||||
|
|
||||||
|
@spec maybe_add_post_picture(map(), Post.t()) :: map()
|
||||||
|
defp maybe_add_post_picture(res, post) do
|
||||||
|
if is_nil(post.picture),
|
||||||
|
do: res,
|
||||||
|
else:
|
||||||
|
Map.update(
|
||||||
|
res,
|
||||||
|
"attachment",
|
||||||
|
[],
|
||||||
|
&(&1 ++
|
||||||
|
[
|
||||||
|
post.picture
|
||||||
|
|> MediaConverter.model_to_as()
|
||||||
|
|> Map.put("name", @banner_picture_name)
|
||||||
|
])
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec maybe_add_inline_media(map(), Post.t()) :: map()
|
||||||
|
defp maybe_add_inline_media(res, post) do
|
||||||
|
medias = Enum.map(post.media, &MediaConverter.model_to_as/1)
|
||||||
|
|
||||||
|
Map.update(
|
||||||
|
res,
|
||||||
|
"attachment",
|
||||||
|
[],
|
||||||
|
&(&1 ++ medias)
|
||||||
|
)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,15 +6,19 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Utils do
|
||||||
alias Mobilizon.{Actors, Events}
|
alias Mobilizon.{Actors, Events}
|
||||||
alias Mobilizon.Actors.Actor
|
alias Mobilizon.Actors.Actor
|
||||||
alias Mobilizon.Events.Tag
|
alias Mobilizon.Events.Tag
|
||||||
|
alias Mobilizon.Medias.Media
|
||||||
alias Mobilizon.Mention
|
alias Mobilizon.Mention
|
||||||
alias Mobilizon.Storage.Repo
|
alias Mobilizon.Storage.Repo
|
||||||
|
|
||||||
alias Mobilizon.Federation.ActivityPub
|
alias Mobilizon.Federation.ActivityPub
|
||||||
|
alias Mobilizon.Federation.ActivityStream.Converter.Media, as: MediaConverter
|
||||||
|
|
||||||
alias Mobilizon.Web.Endpoint
|
alias Mobilizon.Web.Endpoint
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
|
@banner_picture_name "Banner"
|
||||||
|
|
||||||
@spec fetch_tags([String.t()]) :: [Tag.t()]
|
@spec fetch_tags([String.t()]) :: [Tag.t()]
|
||||||
def fetch_tags(tags) when is_list(tags) do
|
def fetch_tags(tags) when is_list(tags) do
|
||||||
Logger.debug("fetching tags")
|
Logger.debug("fetching tags")
|
||||||
|
@ -169,4 +173,62 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Utils do
|
||||||
actor
|
actor
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec process_pictures(map(), integer()) :: Keyword.t()
|
||||||
|
def process_pictures(object, actor_id) do
|
||||||
|
attachements = Map.get(object, "attachment", [])
|
||||||
|
|
||||||
|
media_attachements = get_medias(attachements)
|
||||||
|
|
||||||
|
media_attachements_map =
|
||||||
|
media_attachements
|
||||||
|
|> Enum.map(fn media_attachement ->
|
||||||
|
{media_attachement["url"],
|
||||||
|
MediaConverter.find_or_create_media(media_attachement, actor_id)}
|
||||||
|
end)
|
||||||
|
|> Enum.reduce(%{}, fn {old_url, media}, acc ->
|
||||||
|
case media do
|
||||||
|
{:ok, %Media{} = media} ->
|
||||||
|
Map.put(acc, old_url, media)
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
acc
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
media_attachements_map_urls =
|
||||||
|
media_attachements_map
|
||||||
|
|> Enum.map(fn {old_url, new_media} -> {old_url, new_media.file.url} end)
|
||||||
|
|> Map.new()
|
||||||
|
|
||||||
|
picture_id =
|
||||||
|
with banner when is_map(banner) <- get_banner_picture(attachements),
|
||||||
|
{:ok, %Media{id: picture_id}} <-
|
||||||
|
MediaConverter.find_or_create_media(banner, actor_id) do
|
||||||
|
picture_id
|
||||||
|
else
|
||||||
|
_err ->
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
description = replace_media_urls_in_body(object["content"], media_attachements_map_urls)
|
||||||
|
[description: description, picture_id: picture_id, medias: Map.values(media_attachements_map)]
|
||||||
|
end
|
||||||
|
|
||||||
|
defp replace_media_urls_in_body(body, media_urls),
|
||||||
|
do:
|
||||||
|
Enum.reduce(media_urls, body, fn media_url, body ->
|
||||||
|
replace_media_url_in_body(body, media_url)
|
||||||
|
end)
|
||||||
|
|
||||||
|
defp replace_media_url_in_body(body, {old_url, new_url}),
|
||||||
|
do: String.replace(body, old_url, new_url)
|
||||||
|
|
||||||
|
defp get_medias(attachments) do
|
||||||
|
Enum.filter(attachments, &(&1["type"] == "Document" && &1["name"] != @banner_picture_name))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp get_banner_picture(attachments) do
|
||||||
|
Enum.find(attachments, &(&1["type"] == "Document" && &1["name"] == @banner_picture_name))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,7 +10,6 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
|
||||||
alias Mobilizon.Users.User
|
alias Mobilizon.Users.User
|
||||||
|
|
||||||
alias Mobilizon.GraphQL.API
|
alias Mobilizon.GraphQL.API
|
||||||
alias Mobilizon.GraphQL.Resolvers.Person
|
|
||||||
|
|
||||||
alias Mobilizon.Federation.ActivityPub.Activity
|
alias Mobilizon.Federation.ActivityPub.Activity
|
||||||
import Mobilizon.Web.Gettext
|
import Mobilizon.Web.Gettext
|
||||||
|
@ -35,7 +34,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
|
||||||
) do
|
) do
|
||||||
case {:has_event, Events.get_own_event_by_uuid_with_preload(uuid, user_id)} do
|
case {:has_event, Events.get_own_event_by_uuid_with_preload(uuid, user_id)} do
|
||||||
{:has_event, %Event{} = event} ->
|
{:has_event, %Event{} = event} ->
|
||||||
{:ok, Map.put(event, :organizer_actor, Person.proxify_pictures(event.organizer_actor))}
|
{:ok, event}
|
||||||
|
|
||||||
{:has_event, _} ->
|
{:has_event, _} ->
|
||||||
{:error, :event_not_found}
|
{:error, :event_not_found}
|
||||||
|
@ -51,7 +50,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
|
||||||
{:has_event, Events.get_public_event_by_uuid_with_preload(uuid)},
|
{:has_event, Events.get_public_event_by_uuid_with_preload(uuid)},
|
||||||
{:access_valid, true} <-
|
{:access_valid, true} <-
|
||||||
{:access_valid, Map.has_key?(context, :current_user) || check_event_access(event)} do
|
{:access_valid, Map.has_key?(context, :current_user) || check_event_access(event)} do
|
||||||
{:ok, Map.put(event, :organizer_actor, Person.proxify_pictures(event.organizer_actor))}
|
{:ok, event}
|
||||||
else
|
else
|
||||||
{:has_event, _} ->
|
{:has_event, _} ->
|
||||||
find_private_event(parent, args, resolution)
|
find_private_event(parent, args, resolution)
|
||||||
|
|
|
@ -8,7 +8,6 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
|
||||||
alias Mobilizon.Actors.{Actor, Member}
|
alias Mobilizon.Actors.{Actor, Member}
|
||||||
alias Mobilizon.Federation.ActivityPub
|
alias Mobilizon.Federation.ActivityPub
|
||||||
alias Mobilizon.GraphQL.API
|
alias Mobilizon.GraphQL.API
|
||||||
alias Mobilizon.GraphQL.Resolvers.Person
|
|
||||||
alias Mobilizon.Users.User
|
alias Mobilizon.Users.User
|
||||||
alias Mobilizon.Web.Upload
|
alias Mobilizon.Web.Upload
|
||||||
import Mobilizon.Web.Gettext
|
import Mobilizon.Web.Gettext
|
||||||
|
@ -30,8 +29,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
|
||||||
with {:ok, %Actor{id: group_id} = group} <-
|
with {:ok, %Actor{id: group_id} = group} <-
|
||||||
ActivityPub.find_or_make_group_from_nickname(name),
|
ActivityPub.find_or_make_group_from_nickname(name),
|
||||||
{:actor, %Actor{id: actor_id} = _actor} <- {:actor, Users.get_actor_for_user(user)},
|
{:actor, %Actor{id: actor_id} = _actor} <- {:actor, Users.get_actor_for_user(user)},
|
||||||
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)},
|
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)} do
|
||||||
group <- Person.proxify_pictures(group) do
|
|
||||||
{:ok, group}
|
{:ok, group}
|
||||||
else
|
else
|
||||||
{:member, false} ->
|
{:member, false} ->
|
||||||
|
@ -44,7 +42,6 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
|
||||||
|
|
||||||
def find_group(_parent, %{preferred_username: name}, _resolution) do
|
def find_group(_parent, %{preferred_username: name}, _resolution) do
|
||||||
with {:ok, actor} <- ActivityPub.find_or_make_group_from_nickname(name),
|
with {:ok, actor} <- ActivityPub.find_or_make_group_from_nickname(name),
|
||||||
%Actor{} = actor <- Person.proxify_pictures(actor),
|
|
||||||
%Actor{} = actor <- restrict_fields_for_non_member_request(actor) do
|
%Actor{} = actor <- restrict_fields_for_non_member_request(actor) do
|
||||||
{:ok, actor}
|
{:ok, actor}
|
||||||
else
|
else
|
||||||
|
|
|
@ -6,7 +6,6 @@ defmodule Mobilizon.GraphQL.Resolvers.Participant do
|
||||||
alias Mobilizon.Actors.Actor
|
alias Mobilizon.Actors.Actor
|
||||||
alias Mobilizon.Events.{Event, Participant}
|
alias Mobilizon.Events.{Event, Participant}
|
||||||
alias Mobilizon.GraphQL.API.Participations
|
alias Mobilizon.GraphQL.API.Participations
|
||||||
alias Mobilizon.GraphQL.Resolvers.Person
|
|
||||||
alias Mobilizon.Users.User
|
alias Mobilizon.Users.User
|
||||||
alias Mobilizon.Web.Email
|
alias Mobilizon.Web.Email
|
||||||
alias Mobilizon.Web.Email.Checker
|
alias Mobilizon.Web.Email.Checker
|
||||||
|
@ -114,7 +113,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Participant do
|
||||||
%Participant{} = participant <-
|
%Participant{} = participant <-
|
||||||
participant
|
participant
|
||||||
|> Map.put(:event, event)
|
|> Map.put(:event, event)
|
||||||
|> Map.put(:actor, Person.proxify_pictures(actor)) do
|
|> Map.put(:actor, actor) do
|
||||||
{:ok, participant}
|
{:ok, participant}
|
||||||
else
|
else
|
||||||
{:maximum_attendee_capacity, _} ->
|
{:maximum_attendee_capacity, _} ->
|
||||||
|
|
|
@ -15,15 +15,14 @@ defmodule Mobilizon.GraphQL.Resolvers.Person do
|
||||||
alias Mobilizon.Federation.ActivityPub
|
alias Mobilizon.Federation.ActivityPub
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
alias Mobilizon.Web.{MediaProxy, Upload}
|
alias Mobilizon.Web.Upload
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Get a person
|
Get a person
|
||||||
"""
|
"""
|
||||||
def get_person(_parent, %{id: id}, %{context: %{current_user: %User{role: role}}}) do
|
def get_person(_parent, %{id: id}, %{context: %{current_user: %User{role: role}}}) do
|
||||||
with %Actor{suspended: suspended} = actor <- Actors.get_actor_with_preload(id, true),
|
with %Actor{suspended: suspended} = actor <- Actors.get_actor_with_preload(id, true),
|
||||||
true <- suspended == false or is_moderator(role),
|
true <- suspended == false or is_moderator(role) do
|
||||||
actor <- proxify_pictures(actor) do
|
|
||||||
{:ok, actor}
|
{:ok, actor}
|
||||||
else
|
else
|
||||||
_ ->
|
_ ->
|
||||||
|
@ -31,6 +30,8 @@ defmodule Mobilizon.GraphQL.Resolvers.Person do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_person(_parent, _args, _resolution), do: {:error, :unauthorized}
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Find a person
|
Find a person
|
||||||
"""
|
"""
|
||||||
|
@ -39,8 +40,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Person do
|
||||||
}) do
|
}) do
|
||||||
with {:ok, %Actor{id: actor_id} = actor} <-
|
with {:ok, %Actor{id: actor_id} = actor} <-
|
||||||
ActivityPub.find_or_make_actor_from_nickname(preferred_username),
|
ActivityPub.find_or_make_actor_from_nickname(preferred_username),
|
||||||
{:own, {:is_owned, _}} <- {:own, User.owns_actor(user, actor_id)},
|
{:own, {:is_owned, _}} <- {:own, User.owns_actor(user, actor_id)} do
|
||||||
actor <- proxify_pictures(actor) do
|
|
||||||
{:ok, actor}
|
{:ok, actor}
|
||||||
else
|
else
|
||||||
{:own, nil} ->
|
{:own, nil} ->
|
||||||
|
@ -120,9 +120,12 @@ defmodule Mobilizon.GraphQL.Resolvers.Person do
|
||||||
args = Map.put(args, :user_id, user.id)
|
args = Map.put(args, :user_id, user.id)
|
||||||
|
|
||||||
with args <- Map.update(args, :preferred_username, "", &String.downcase/1),
|
with args <- Map.update(args, :preferred_username, "", &String.downcase/1),
|
||||||
args <- save_attached_pictures(args),
|
{:picture, args} when is_map(args) <- {:picture, save_attached_pictures(args)},
|
||||||
{:ok, %Actor{} = new_person} <- Actors.new_person(args) do
|
{:ok, %Actor{} = new_person} <- Actors.new_person(args) do
|
||||||
{:ok, new_person}
|
{:ok, new_person}
|
||||||
|
else
|
||||||
|
{:picture, {:error, :file_too_large}} ->
|
||||||
|
{:error, dgettext("errors", "The provided picture is too heavy")}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -144,10 +147,13 @@ defmodule Mobilizon.GraphQL.Resolvers.Person do
|
||||||
with {:find_actor, %Actor{} = actor} <-
|
with {:find_actor, %Actor{} = actor} <-
|
||||||
{:find_actor, Actors.get_actor(id)},
|
{:find_actor, Actors.get_actor(id)},
|
||||||
{:is_owned, %Actor{}} <- User.owns_actor(user, actor.id),
|
{:is_owned, %Actor{}} <- User.owns_actor(user, actor.id),
|
||||||
args <- save_attached_pictures(args),
|
{:picture, args} when is_map(args) <- {:picture, save_attached_pictures(args)},
|
||||||
{:ok, _activity, %Actor{} = actor} <- ActivityPub.update(actor, args, true) do
|
{:ok, _activity, %Actor{} = actor} <- ActivityPub.update(actor, args, true) do
|
||||||
{:ok, actor}
|
{:ok, actor}
|
||||||
else
|
else
|
||||||
|
{:picture, {:error, :file_too_large}} ->
|
||||||
|
{:error, dgettext("errors", "The provided picture is too heavy")}
|
||||||
|
|
||||||
{:find_actor, nil} ->
|
{:find_actor, nil} ->
|
||||||
{:error, dgettext("errors", "Profile not found")}
|
{:error, dgettext("errors", "Profile not found")}
|
||||||
|
|
||||||
|
@ -199,18 +205,27 @@ defmodule Mobilizon.GraphQL.Resolvers.Person do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp save_attached_pictures(args) do
|
defp save_attached_pictures(args) do
|
||||||
Enum.reduce([:avatar, :banner], args, fn key, args ->
|
with args when is_map(args) <- save_attached_picture(args, :avatar),
|
||||||
if Map.has_key?(args, key) && !is_nil(args[key][:media]) do
|
args when is_map(args) <- save_attached_picture(args, :banner) do
|
||||||
media = args[key][:media]
|
args
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
with {:ok, %{name: name, url: url, content_type: content_type, size: _size}} <-
|
defp save_attached_picture(args, key) do
|
||||||
Upload.store(media.file, type: key, description: media.alt) do
|
if Map.has_key?(args, key) && !is_nil(args[key][:media]) do
|
||||||
Map.put(args, key, %{"name" => name, "url" => url, "mediaType" => content_type})
|
with media when is_map(media) <- save_picture(args[key][:media], key) do
|
||||||
end
|
Map.put(args, key, media)
|
||||||
else
|
|
||||||
args
|
|
||||||
end
|
end
|
||||||
end)
|
else
|
||||||
|
args
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp save_picture(media, key) do
|
||||||
|
with {:ok, %{name: name, url: url, content_type: content_type, size: _size}} <-
|
||||||
|
Upload.store(media.file, type: key, description: media.alt) do
|
||||||
|
%{"name" => name, "url" => url, "mediaType" => content_type}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
@ -223,10 +238,13 @@ defmodule Mobilizon.GraphQL.Resolvers.Person do
|
||||||
{:no_actor, true} <- {:no_actor, no_actor},
|
{:no_actor, true} <- {:no_actor, no_actor},
|
||||||
args <- Map.update(args, :preferred_username, "", &String.downcase/1),
|
args <- Map.update(args, :preferred_username, "", &String.downcase/1),
|
||||||
args <- Map.put(args, :user_id, user.id),
|
args <- Map.put(args, :user_id, user.id),
|
||||||
args <- save_attached_pictures(args),
|
{:picture, args} when is_map(args) <- {:picture, save_attached_pictures(args)},
|
||||||
{:ok, %Actor{} = new_person} <- Actors.new_person(args, true) do
|
{:ok, %Actor{} = new_person} <- Actors.new_person(args, true) do
|
||||||
{:ok, new_person}
|
{:ok, new_person}
|
||||||
else
|
else
|
||||||
|
{:picture, {:error, :file_too_large}} ->
|
||||||
|
{:error, dgettext("errors", "The provided picture is too heavy")}
|
||||||
|
|
||||||
{:error, :user_not_found} ->
|
{:error, :user_not_found} ->
|
||||||
{:error, dgettext("errors", "No user with this email was found")}
|
{:error, dgettext("errors", "No user with this email was found")}
|
||||||
|
|
||||||
|
@ -298,12 +316,6 @@ defmodule Mobilizon.GraphQL.Resolvers.Person do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def proxify_pictures(%Actor{} = actor) do
|
|
||||||
actor
|
|
||||||
|> proxify_avatar
|
|
||||||
|> proxify_banner
|
|
||||||
end
|
|
||||||
|
|
||||||
def user_for_person(%Actor{type: :Person, user_id: user_id}, _args, %{
|
def user_for_person(%Actor{type: :Person, user_id: user_id}, _args, %{
|
||||||
context: %{current_user: %User{role: role}}
|
context: %{current_user: %User{role: role}}
|
||||||
})
|
})
|
||||||
|
@ -343,20 +355,4 @@ defmodule Mobilizon.GraphQL.Resolvers.Person do
|
||||||
defp last_admin_of_a_group?(actor_id) do
|
defp last_admin_of_a_group?(actor_id) do
|
||||||
length(Actors.list_group_ids_where_last_administrator(actor_id)) > 0
|
length(Actors.list_group_ids_where_last_administrator(actor_id)) > 0
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec proxify_avatar(Actor.t()) :: Actor.t()
|
|
||||||
defp proxify_avatar(%Actor{avatar: %{url: avatar_url} = avatar} = actor) do
|
|
||||||
actor |> Map.put(:avatar, avatar |> Map.put(:url, MediaProxy.url(avatar_url)))
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec proxify_avatar(Actor.t()) :: Actor.t()
|
|
||||||
defp proxify_avatar(%Actor{} = actor), do: actor
|
|
||||||
|
|
||||||
@spec proxify_banner(Actor.t()) :: Actor.t()
|
|
||||||
defp proxify_banner(%Actor{banner: %{url: banner_url} = banner} = actor) do
|
|
||||||
actor |> Map.put(:banner, banner |> Map.put(:url, MediaProxy.url(banner_url)))
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec proxify_banner(Actor.t()) :: Actor.t()
|
|
||||||
defp proxify_banner(%Actor{} = actor), do: actor
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -73,6 +73,12 @@ defmodule Mix.Tasks.Mobilizon.Actors.Refresh do
|
||||||
|
|
||||||
{:actor, nil} ->
|
{:actor, nil} ->
|
||||||
shell_error("Error: No such actor")
|
shell_error("Error: No such actor")
|
||||||
|
|
||||||
|
{:error, err} when is_binary(err) ->
|
||||||
|
shell_error(err)
|
||||||
|
|
||||||
|
_err ->
|
||||||
|
shell_error("Error while refreshing actor #{preferred_username}")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -13,11 +13,13 @@ defmodule Mobilizon.Actors do
|
||||||
alias Mobilizon.Actors.{Actor, Bot, Follower, Member}
|
alias Mobilizon.Actors.{Actor, Bot, Follower, Member}
|
||||||
alias Mobilizon.Addresses.Address
|
alias Mobilizon.Addresses.Address
|
||||||
alias Mobilizon.{Crypto, Events}
|
alias Mobilizon.{Crypto, Events}
|
||||||
|
alias Mobilizon.Events.FeedToken
|
||||||
alias Mobilizon.Federation.ActivityPub
|
alias Mobilizon.Federation.ActivityPub
|
||||||
alias Mobilizon.Medias.File
|
alias Mobilizon.Medias.File
|
||||||
alias Mobilizon.Service.Workers
|
alias Mobilizon.Service.Workers
|
||||||
alias Mobilizon.Storage.{Page, Repo}
|
alias Mobilizon.Storage.{Page, Repo}
|
||||||
alias Mobilizon.Users
|
alias Mobilizon.Users
|
||||||
|
alias Mobilizon.Users.User
|
||||||
alias Mobilizon.Web.Email.Group
|
alias Mobilizon.Web.Email.Group
|
||||||
alias Mobilizon.Web.Upload
|
alias Mobilizon.Web.Upload
|
||||||
|
|
||||||
|
@ -215,18 +217,35 @@ defmodule Mobilizon.Actors do
|
||||||
def new_person(args, default_actor \\ false) do
|
def new_person(args, default_actor \\ false) do
|
||||||
args = Map.put(args, :keys, Crypto.generate_rsa_2048_private_key())
|
args = Map.put(args, :keys, Crypto.generate_rsa_2048_private_key())
|
||||||
|
|
||||||
with {:ok, %Actor{id: person_id} = person} <-
|
multi =
|
||||||
%Actor{}
|
Multi.new()
|
||||||
|> Actor.registration_changeset(args)
|
|> Multi.insert(:person, Actor.registration_changeset(%Actor{}, args))
|
||||||
|> Repo.insert() do
|
|> Multi.insert(:token, fn %{person: person} ->
|
||||||
Events.create_feed_token(%{user_id: args.user_id, actor_id: person.id})
|
FeedToken.changeset(%FeedToken{}, %{
|
||||||
|
user_id: args.user_id,
|
||||||
|
actor_id: person.id,
|
||||||
|
token: Ecto.UUID.generate()
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
|
||||||
|
multi =
|
||||||
if default_actor do
|
if default_actor do
|
||||||
user = Users.get_user!(args.user_id)
|
user = Users.get_user!(args.user_id)
|
||||||
Users.update_user(user, %{default_actor_id: person_id})
|
|
||||||
|
Multi.update(multi, :user, fn %{person: person} ->
|
||||||
|
User.changeset(user, %{default_actor_id: person.id})
|
||||||
|
end)
|
||||||
|
else
|
||||||
|
multi
|
||||||
end
|
end
|
||||||
|
|
||||||
{:ok, person}
|
case Repo.transaction(multi) do
|
||||||
|
{:ok, %{person: %Actor{} = person}} ->
|
||||||
|
{:ok, person}
|
||||||
|
|
||||||
|
{:error, _step, err, _} ->
|
||||||
|
Logger.error("Error while creating a new person")
|
||||||
|
{:error, err}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ defmodule Mobilizon.Posts do
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
@post_preloads [:author, :attributed_to, :picture]
|
@post_preloads [:author, :attributed_to, :picture, :media]
|
||||||
|
|
||||||
import EctoEnum
|
import EctoEnum
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ defmodule Mobilizon.Service.Export.Feed do
|
||||||
alias Mobilizon.Storage.Page
|
alias Mobilizon.Storage.Page
|
||||||
alias Mobilizon.Users.User
|
alias Mobilizon.Users.User
|
||||||
|
|
||||||
alias Mobilizon.Web.{Endpoint, MediaProxy}
|
alias Mobilizon.Web.Endpoint
|
||||||
alias Mobilizon.Web.Router.Helpers, as: Routes
|
alias Mobilizon.Web.Router.Helpers, as: Routes
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
@ -85,14 +85,14 @@ defmodule Mobilizon.Service.Export.Feed do
|
||||||
|
|
||||||
feed =
|
feed =
|
||||||
if actor.avatar do
|
if actor.avatar do
|
||||||
feed |> Feed.icon(actor.avatar.url |> MediaProxy.url())
|
feed |> Feed.icon(actor.avatar.url)
|
||||||
else
|
else
|
||||||
feed
|
feed
|
||||||
end
|
end
|
||||||
|
|
||||||
feed =
|
feed =
|
||||||
if actor.banner do
|
if actor.banner do
|
||||||
feed |> Feed.logo(actor.banner.url |> MediaProxy.url())
|
feed |> Feed.logo(actor.banner.url)
|
||||||
else
|
else
|
||||||
feed
|
feed
|
||||||
end
|
end
|
||||||
|
|
|
@ -61,7 +61,8 @@ defmodule Mobilizon.Service.Formatter.DefaultScrubbler do
|
||||||
"height",
|
"height",
|
||||||
"class",
|
"class",
|
||||||
"title",
|
"title",
|
||||||
"alt"
|
"alt",
|
||||||
|
"data-media-id"
|
||||||
])
|
])
|
||||||
|
|
||||||
Meta.allow_tag_with_this_attribute_values(:span, "class", ["h-card", "mention"])
|
Meta.allow_tag_with_this_attribute_values(:span, "class", ["h-card", "mention"])
|
||||||
|
|
22
lib/service/http/remote_media_downloader_client.ex
Normal file
22
lib/service/http/remote_media_downloader_client.ex
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
defmodule Mobilizon.Service.HTTP.RemoteMediaDownloaderClient do
|
||||||
|
@moduledoc """
|
||||||
|
Tesla HTTP Basic Client that fetches HTML to extract metadata preview
|
||||||
|
"""
|
||||||
|
|
||||||
|
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
|
|
@ -3,7 +3,6 @@ defimpl Mobilizon.Service.Metadata, for: Mobilizon.Actors.Actor do
|
||||||
alias Phoenix.HTML.Tag
|
alias Phoenix.HTML.Tag
|
||||||
alias Mobilizon.Actors.Actor
|
alias Mobilizon.Actors.Actor
|
||||||
alias Mobilizon.Web.JsonLD.ObjectView
|
alias Mobilizon.Web.JsonLD.ObjectView
|
||||||
alias Mobilizon.Web.MediaProxy
|
|
||||||
import Mobilizon.Service.Metadata.Utils, only: [process_description: 2, default_description: 1]
|
import Mobilizon.Service.Metadata.Utils, only: [process_description: 2, default_description: 1]
|
||||||
|
|
||||||
def build_tags(_actor, _locale \\ "en")
|
def build_tags(_actor, _locale \\ "en")
|
||||||
|
@ -36,7 +35,7 @@ defimpl Mobilizon.Service.Metadata, for: Mobilizon.Actors.Actor do
|
||||||
tags
|
tags
|
||||||
else
|
else
|
||||||
tags ++
|
tags ++
|
||||||
[Tag.tag(:meta, property: "og:image", content: actor.avatar.url |> MediaProxy.url())]
|
[Tag.tag(:meta, property: "og:image", content: actor.avatar.url)]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,6 @@ defimpl Mobilizon.Service.Metadata, for: Mobilizon.Events.Event do
|
||||||
alias Phoenix.HTML.Tag
|
alias Phoenix.HTML.Tag
|
||||||
alias Mobilizon.Events.Event
|
alias Mobilizon.Events.Event
|
||||||
alias Mobilizon.Web.JsonLD.ObjectView
|
alias Mobilizon.Web.JsonLD.ObjectView
|
||||||
alias Mobilizon.Web.MediaProxy
|
|
||||||
import Mobilizon.Service.Metadata.Utils, only: [process_description: 2, strip_tags: 1]
|
import Mobilizon.Service.Metadata.Utils, only: [process_description: 2, strip_tags: 1]
|
||||||
|
|
||||||
def build_tags(%Event{} = event, locale \\ "en") do
|
def build_tags(%Event{} = event, locale \\ "en") do
|
||||||
|
@ -28,7 +27,7 @@ defimpl Mobilizon.Service.Metadata, for: Mobilizon.Events.Event do
|
||||||
[
|
[
|
||||||
Tag.tag(:meta,
|
Tag.tag(:meta,
|
||||||
property: "og:image",
|
property: "og:image",
|
||||||
content: event.picture.file.url |> MediaProxy.url()
|
content: event.picture.file.url
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
|
@ -47,6 +47,14 @@ defmodule Mobilizon.Service.RichMedia.Parser do
|
||||||
{:error, "Cachex error: #{inspect(e)}"}
|
{:error, "Cachex error: #{inspect(e)}"}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Get a filename for the fetched data, using the response header or the last part of the URL
|
||||||
|
"""
|
||||||
|
@spec get_filename_from_response(Enum.t(), String.t()) :: String.t() | nil
|
||||||
|
def get_filename_from_response(response_headers, url) do
|
||||||
|
get_filename_from_headers(response_headers) || get_filename_from_url(url)
|
||||||
|
end
|
||||||
|
|
||||||
@spec parse_url(String.t(), Enum.t()) :: {:ok, map()} | {:error, any()}
|
@spec parse_url(String.t(), Enum.t()) :: {:ok, map()} | {:error, any()}
|
||||||
defp parse_url(url, options \\ []) do
|
defp parse_url(url, options \\ []) do
|
||||||
user_agent = Keyword.get(options, :user_agent, Config.instance_user_agent())
|
user_agent = Keyword.get(options, :user_agent, Config.instance_user_agent())
|
||||||
|
|
|
@ -1,50 +0,0 @@
|
||||||
# Portions of this file are derived from Pleroma:
|
|
||||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
# Upstream: https://git.pleroma.social/pleroma/pleroma/blob/develop/lib/pleroma/web/media_proxy/controller.ex
|
|
||||||
|
|
||||||
defmodule Mobilizon.Web.MediaProxyController do
|
|
||||||
use Mobilizon.Web, :controller
|
|
||||||
|
|
||||||
alias Plug.Conn
|
|
||||||
|
|
||||||
alias Mobilizon.Config
|
|
||||||
|
|
||||||
alias Mobilizon.Web.MediaProxy
|
|
||||||
alias Mobilizon.Web.ReverseProxy
|
|
||||||
|
|
||||||
@default_proxy_opts [max_body_length: 25 * 1_048_576, http: [follow_redirect: true]]
|
|
||||||
|
|
||||||
def remote(conn, %{"sig" => sig64, "url" => url64} = params) do
|
|
||||||
with config <- Config.get([:media_proxy], []),
|
|
||||||
true <- Keyword.get(config, :enabled, false),
|
|
||||||
{:ok, url} <- MediaProxy.decode_url(sig64, url64),
|
|
||||||
:ok <- filename_matches(Map.has_key?(params, "filename"), conn.request_path, url) do
|
|
||||||
ReverseProxy.call(conn, url, Keyword.get(config, :proxy_opts, @default_proxy_opts))
|
|
||||||
else
|
|
||||||
false ->
|
|
||||||
send_resp(conn, 404, Conn.Status.reason_phrase(404))
|
|
||||||
|
|
||||||
{:error, :invalid_signature} ->
|
|
||||||
send_resp(conn, 403, Conn.Status.reason_phrase(403))
|
|
||||||
|
|
||||||
{:wrong_filename, filename} ->
|
|
||||||
redirect(conn, external: MediaProxy.build_url(sig64, url64, filename))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def filename_matches(has_filename, path, url) do
|
|
||||||
filename =
|
|
||||||
url
|
|
||||||
|> MediaProxy.filename()
|
|
||||||
|> URI.decode()
|
|
||||||
|
|
||||||
path = URI.decode(path)
|
|
||||||
|
|
||||||
if has_filename && filename && Path.basename(path) != filename do
|
|
||||||
{:wrong_filename, filename}
|
|
||||||
else
|
|
||||||
:ok
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,91 +0,0 @@
|
||||||
# Portions of this file are derived from Pleroma:
|
|
||||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
# Upstream: https://git.pleroma.social/pleroma/pleroma/blob/develop/lib/pleroma/web/media_proxy/media_proxy.ex
|
|
||||||
|
|
||||||
defmodule Mobilizon.Web.MediaProxy do
|
|
||||||
@moduledoc """
|
|
||||||
Handles proxifying media files
|
|
||||||
"""
|
|
||||||
|
|
||||||
alias Mobilizon.Config
|
|
||||||
|
|
||||||
alias Mobilizon.Web.Endpoint
|
|
||||||
|
|
||||||
@base64_opts [padding: false]
|
|
||||||
|
|
||||||
def url(nil), do: nil
|
|
||||||
|
|
||||||
def url(""), do: nil
|
|
||||||
|
|
||||||
def url("/" <> _ = url), do: url
|
|
||||||
|
|
||||||
def url(url) do
|
|
||||||
config = Application.get_env(:mobilizon, :media_proxy, [])
|
|
||||||
|
|
||||||
if !Keyword.get(config, :enabled, false) or
|
|
||||||
String.starts_with?(url, Endpoint.url()) do
|
|
||||||
url
|
|
||||||
else
|
|
||||||
encode_url(url)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def encode_url(url) do
|
|
||||||
secret = Application.get_env(:mobilizon, Endpoint)[:secret_key_base]
|
|
||||||
|
|
||||||
# Must preserve `%2F` for compatibility with S3
|
|
||||||
# https://git.pleroma.social/pleroma/pleroma/issues/580
|
|
||||||
replacement = get_replacement(url, ":2F:")
|
|
||||||
|
|
||||||
# The URL is url-decoded and encoded again to ensure it is correctly encoded and not twice.
|
|
||||||
base64 =
|
|
||||||
url
|
|
||||||
|> String.replace("%2F", replacement)
|
|
||||||
|> URI.decode()
|
|
||||||
|> URI.encode()
|
|
||||||
|> String.replace(replacement, "%2F")
|
|
||||||
|> Base.url_encode64(@base64_opts)
|
|
||||||
|
|
||||||
sig = :crypto.hmac(:sha, secret, base64)
|
|
||||||
sig64 = sig |> Base.url_encode64(@base64_opts)
|
|
||||||
|
|
||||||
build_url(sig64, base64, filename(url))
|
|
||||||
end
|
|
||||||
|
|
||||||
def decode_url(sig, url) do
|
|
||||||
secret = Application.get_env(:mobilizon, Endpoint)[:secret_key_base]
|
|
||||||
sig = Base.url_decode64!(sig, @base64_opts)
|
|
||||||
local_sig = :crypto.hmac(:sha, secret, url)
|
|
||||||
|
|
||||||
if local_sig == sig do
|
|
||||||
{:ok, Base.url_decode64!(url, @base64_opts)}
|
|
||||||
else
|
|
||||||
{:error, :invalid_signature}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def filename(url_or_path) do
|
|
||||||
if path = URI.parse(url_or_path).path, do: Path.basename(path)
|
|
||||||
end
|
|
||||||
|
|
||||||
def build_url(sig_base64, url_base64, filename \\ nil) do
|
|
||||||
[
|
|
||||||
Config.get([:media_proxy, :base_url], Endpoint.url()),
|
|
||||||
"proxy",
|
|
||||||
sig_base64,
|
|
||||||
url_base64,
|
|
||||||
filename
|
|
||||||
]
|
|
||||||
|> Enum.filter(fn value -> value end)
|
|
||||||
|> Path.join()
|
|
||||||
end
|
|
||||||
|
|
||||||
defp get_replacement(url, replacement) do
|
|
||||||
if String.contains?(url, replacement) do
|
|
||||||
get_replacement(url, replacement <> replacement)
|
|
||||||
else
|
|
||||||
replacement
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -69,8 +69,6 @@ defmodule Mobilizon.Web.ReverseProxy do
|
||||||
|
|
||||||
alias Plug.Conn
|
alias Plug.Conn
|
||||||
|
|
||||||
alias Mobilizon.Web.MediaProxy
|
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
@type option ::
|
@type option ::
|
||||||
|
@ -111,7 +109,7 @@ defmodule Mobilizon.Web.ReverseProxy do
|
||||||
req_headers = build_req_headers(conn.req_headers, opts)
|
req_headers = build_req_headers(conn.req_headers, opts)
|
||||||
|
|
||||||
opts =
|
opts =
|
||||||
if filename = MediaProxy.filename(url) do
|
if filename = filename(url) do
|
||||||
Keyword.put_new(opts, :attachment_name, filename)
|
Keyword.put_new(opts, :attachment_name, filename)
|
||||||
else
|
else
|
||||||
opts
|
opts
|
||||||
|
@ -388,4 +386,8 @@ defmodule Mobilizon.Web.ReverseProxy do
|
||||||
defp increase_read_duration(_) do
|
defp increase_read_duration(_) do
|
||||||
{:ok, :no_duration_limit, :no_duration_limit}
|
{:ok, :no_duration_limit, :no_duration_limit}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def filename(url_or_path) do
|
||||||
|
if path = URI.parse(url_or_path).path, do: Path.basename(path)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -162,13 +162,6 @@ defmodule Mobilizon.Web.Router do
|
||||||
post("/auth/:provider/callback", AuthController, :callback)
|
post("/auth/:provider/callback", AuthController, :callback)
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/proxy/", Mobilizon.Web do
|
|
||||||
pipe_through(:remote_media)
|
|
||||||
|
|
||||||
get("/:sig/:url", MediaProxyController, :remote)
|
|
||||||
get("/:sig/:url/:filename", MediaProxyController, :remote)
|
|
||||||
end
|
|
||||||
|
|
||||||
if Application.fetch_env!(:mobilizon, :env) in [:dev, :e2e] do
|
if Application.fetch_env!(:mobilizon, :env) in [:dev, :e2e] do
|
||||||
# If using Phoenix
|
# If using Phoenix
|
||||||
forward("/sent_emails", Bamboo.SentEmailViewerPlug)
|
forward("/sent_emails", Bamboo.SentEmailViewerPlug)
|
||||||
|
|
|
@ -5,7 +5,7 @@ defmodule Mobilizon.Web.JsonLD.ObjectView do
|
||||||
alias Mobilizon.Addresses.Address
|
alias Mobilizon.Addresses.Address
|
||||||
alias Mobilizon.Events.Event
|
alias Mobilizon.Events.Event
|
||||||
alias Mobilizon.Posts.Post
|
alias Mobilizon.Posts.Post
|
||||||
alias Mobilizon.Web.{Endpoint, MediaProxy}
|
alias Mobilizon.Web.Endpoint
|
||||||
alias Mobilizon.Web.JsonLD.ObjectView
|
alias Mobilizon.Web.JsonLD.ObjectView
|
||||||
|
|
||||||
def render("group.json", %{group: %Actor{} = group}) do
|
def render("group.json", %{group: %Actor{} = group}) do
|
||||||
|
@ -41,7 +41,7 @@ defmodule Mobilizon.Web.JsonLD.ObjectView do
|
||||||
"image" =>
|
"image" =>
|
||||||
if(event.picture,
|
if(event.picture,
|
||||||
do: [
|
do: [
|
||||||
event.picture.file.url |> MediaProxy.url()
|
event.picture.file.url
|
||||||
],
|
],
|
||||||
else: ["#{Endpoint.url()}/img/mobilizon_default_card.png"]
|
else: ["#{Endpoint.url()}/img/mobilizon_default_card.png"]
|
||||||
)
|
)
|
||||||
|
|
|
@ -8,6 +8,7 @@ defmodule Mobilizon.Web.PageView do
|
||||||
alias Mobilizon.Actors.Actor
|
alias Mobilizon.Actors.Actor
|
||||||
alias Mobilizon.Discussions.{Comment, Discussion}
|
alias Mobilizon.Discussions.{Comment, Discussion}
|
||||||
alias Mobilizon.Events.Event
|
alias Mobilizon.Events.Event
|
||||||
|
alias Mobilizon.Posts.Post
|
||||||
alias Mobilizon.Resources.Resource
|
alias Mobilizon.Resources.Resource
|
||||||
alias Mobilizon.Tombstone
|
alias Mobilizon.Tombstone
|
||||||
|
|
||||||
|
@ -54,6 +55,12 @@ defmodule Mobilizon.Web.PageView do
|
||||||
|> Map.merge(Utils.make_json_ld_header())
|
|> Map.merge(Utils.make_json_ld_header())
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def render("post.activity-json", %{conn: %{assigns: %{object: %Post{} = post}}}) do
|
||||||
|
post
|
||||||
|
|> Convertible.model_to_as()
|
||||||
|
|> Map.merge(Utils.make_json_ld_header())
|
||||||
|
end
|
||||||
|
|
||||||
def render(page, %{object: object, conn: conn} = _assigns)
|
def render(page, %{object: object, conn: conn} = _assigns)
|
||||||
when page in ["actor.html", "event.html", "comment.html", "post.html"] do
|
when page in ["actor.html", "event.html", "comment.html", "post.html"] do
|
||||||
locale = get_locale(conn)
|
locale = get_locale(conn)
|
||||||
|
|
2
mix.exs
2
mix.exs
|
@ -264,7 +264,6 @@ defmodule Mobilizon.Mixfile do
|
||||||
Mobilizon.Web.Plugs.UploadedMedia,
|
Mobilizon.Web.Plugs.UploadedMedia,
|
||||||
Mobilizon.Web.FallbackController,
|
Mobilizon.Web.FallbackController,
|
||||||
Mobilizon.Web.FeedController,
|
Mobilizon.Web.FeedController,
|
||||||
Mobilizon.Web.MediaProxyController,
|
|
||||||
Mobilizon.Web.PageController,
|
Mobilizon.Web.PageController,
|
||||||
Mobilizon.Web.ChangesetView,
|
Mobilizon.Web.ChangesetView,
|
||||||
Mobilizon.Web.JsonLD.ObjectView,
|
Mobilizon.Web.JsonLD.ObjectView,
|
||||||
|
@ -295,7 +294,6 @@ defmodule Mobilizon.Mixfile do
|
||||||
Mobilizon.Web.Upload.MIME,
|
Mobilizon.Web.Upload.MIME,
|
||||||
Mobilizon.Web.Upload.Uploader,
|
Mobilizon.Web.Upload.Uploader,
|
||||||
Mobilizon.Web.Upload.Uploader.Local,
|
Mobilizon.Web.Upload.Uploader.Local,
|
||||||
Mobilizon.Web.MediaProxy,
|
|
||||||
Mobilizon.Web.ReverseProxy
|
Mobilizon.Web.ReverseProxy
|
||||||
],
|
],
|
||||||
Geospatial: [
|
Geospatial: [
|
||||||
|
|
|
@ -45,13 +45,13 @@ defmodule Mobilizon.Federation.ActivityPubTest do
|
||||||
use_cassette "activity_pub/fetch_tcit@framapiaf.org" do
|
use_cassette "activity_pub/fetch_tcit@framapiaf.org" do
|
||||||
assert {:ok,
|
assert {:ok,
|
||||||
%Actor{preferred_username: "tcit", domain: "framapiaf.org", visibility: :public} =
|
%Actor{preferred_username: "tcit", domain: "framapiaf.org", visibility: :public} =
|
||||||
actor} = ActivityPub.make_actor_from_nickname("tcit@framapiaf.org")
|
_actor} = ActivityPub.make_actor_from_nickname("tcit@framapiaf.org")
|
||||||
end
|
end
|
||||||
|
|
||||||
use_cassette "activity_pub/fetch_tcit@framapiaf.org_not_discoverable" do
|
use_cassette "activity_pub/fetch_tcit@framapiaf.org_not_discoverable" do
|
||||||
assert {:ok,
|
assert {:ok,
|
||||||
%Actor{preferred_username: "tcit", domain: "framapiaf.org", visibility: :unlisted} =
|
%Actor{preferred_username: "tcit", domain: "framapiaf.org", visibility: :unlisted} =
|
||||||
actor} = ActivityPub.make_actor_from_nickname("tcit@framapiaf.org")
|
_actor} = ActivityPub.make_actor_from_nickname("tcit@framapiaf.org")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -111,117 +111,110 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier.UpdateTest do
|
||||||
|
|
||||||
describe "handle incoming updates activities for group posts" do
|
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
|
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} =
|
||||||
%Actor{url: remote_actor_url} =
|
remote_actor =
|
||||||
remote_actor =
|
insert(:actor,
|
||||||
insert(:actor,
|
domain: "remote.domain",
|
||||||
domain: "remote.domain",
|
url: "https://remote.domain/@remote",
|
||||||
url: "https://remote.domain/@remote",
|
preferred_username: "remote"
|
||||||
preferred_username: "remote"
|
)
|
||||||
)
|
|
||||||
|
|
||||||
group = insert(:group)
|
group = insert(:group)
|
||||||
%Member{} = member = insert(:member, actor: remote_actor, parent: group, role: :moderator)
|
%Member{} = member = insert(:member, actor: remote_actor, parent: group, role: :moderator)
|
||||||
%Post{} = post = insert(:post, attributed_to: group)
|
%Post{} = post = insert(:post, attributed_to: group)
|
||||||
|
|
||||||
data = Convertible.model_to_as(post)
|
data = Convertible.model_to_as(post)
|
||||||
refute is_nil(Posts.get_post_by_url(data["id"]))
|
refute is_nil(Posts.get_post_by_url(data["id"]))
|
||||||
|
|
||||||
update_data = File.read!("test/fixtures/mastodon-update.json") |> Jason.decode!()
|
update_data = File.read!("test/fixtures/mastodon-update.json") |> Jason.decode!()
|
||||||
|
|
||||||
object =
|
object =
|
||||||
data
|
data
|
||||||
|> Map.put("name", "My updated post")
|
|> Map.put("name", "My updated post")
|
||||||
|> Map.put("type", "Article")
|
|> Map.put("type", "Article")
|
||||||
|
|
||||||
update_data =
|
update_data =
|
||||||
update_data
|
update_data
|
||||||
|> Map.put("actor", remote_actor_url)
|
|> Map.put("actor", remote_actor_url)
|
||||||
|> Map.put("object", object)
|
|> Map.put("object", object)
|
||||||
|
|
||||||
{:ok, %Activity{data: data, local: false}, _} =
|
{:ok, %Activity{data: data, local: false}, _} = Transmogrifier.handle_incoming(update_data)
|
||||||
Transmogrifier.handle_incoming(update_data)
|
|
||||||
|
|
||||||
%Post{id: updated_post_id, title: updated_post_title} =
|
%Post{id: updated_post_id, title: updated_post_title} =
|
||||||
Posts.get_post_by_url(data["object"]["id"])
|
Posts.get_post_by_url(data["object"]["id"])
|
||||||
|
|
||||||
assert updated_post_id == post.id
|
assert updated_post_id == post.id
|
||||||
assert updated_post_title == "My updated post"
|
assert updated_post_title == "My updated post"
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it works for incoming update activities on group posts" do
|
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} =
|
||||||
%Actor{url: remote_actor_url} =
|
remote_actor =
|
||||||
remote_actor =
|
insert(:actor,
|
||||||
insert(:actor,
|
domain: "remote.domain",
|
||||||
domain: "remote.domain",
|
url: "https://remote.domain/@remote",
|
||||||
url: "https://remote.domain/@remote",
|
preferred_username: "remote"
|
||||||
preferred_username: "remote"
|
)
|
||||||
)
|
|
||||||
|
|
||||||
group = insert(:group)
|
group = insert(:group)
|
||||||
%Member{} = member = insert(:member, actor: remote_actor, parent: group)
|
%Member{} = member = insert(:member, actor: remote_actor, parent: group)
|
||||||
%Post{} = post = insert(:post, attributed_to: group)
|
%Post{} = post = insert(:post, attributed_to: group)
|
||||||
|
|
||||||
data = Convertible.model_to_as(post)
|
data = Convertible.model_to_as(post)
|
||||||
refute is_nil(Posts.get_post_by_url(data["id"]))
|
refute is_nil(Posts.get_post_by_url(data["id"]))
|
||||||
|
|
||||||
update_data = File.read!("test/fixtures/mastodon-update.json") |> Jason.decode!()
|
update_data = File.read!("test/fixtures/mastodon-update.json") |> Jason.decode!()
|
||||||
|
|
||||||
object =
|
object =
|
||||||
data
|
data
|
||||||
|> Map.put("name", "My updated post")
|
|> Map.put("name", "My updated post")
|
||||||
|> Map.put("type", "Article")
|
|> Map.put("type", "Article")
|
||||||
|
|
||||||
update_data =
|
update_data =
|
||||||
update_data
|
update_data
|
||||||
|> Map.put("actor", remote_actor_url)
|
|> Map.put("actor", remote_actor_url)
|
||||||
|> Map.put("object", object)
|
|> Map.put("object", object)
|
||||||
|
|
||||||
:error = Transmogrifier.handle_incoming(update_data)
|
:error = Transmogrifier.handle_incoming(update_data)
|
||||||
|
|
||||||
%Post{id: updated_post_id, title: updated_post_title} = Posts.get_post_by_url(data["id"])
|
%Post{id: updated_post_id, title: updated_post_title} = Posts.get_post_by_url(data["id"])
|
||||||
|
|
||||||
assert updated_post_id == post.id
|
assert updated_post_id == post.id
|
||||||
refute updated_post_title == "My updated post"
|
refute updated_post_title == "My updated post"
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it fails for incoming update activities on group posts when the actor is not a member from the group" do
|
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} =
|
||||||
%Actor{url: remote_actor_url} =
|
insert(:actor,
|
||||||
insert(:actor,
|
domain: "remote.domain",
|
||||||
domain: "remote.domain",
|
url: "https://remote.domain/@remote",
|
||||||
url: "https://remote.domain/@remote",
|
preferred_username: "remote"
|
||||||
preferred_username: "remote"
|
)
|
||||||
)
|
|
||||||
|
|
||||||
group = insert(:group)
|
group = insert(:group)
|
||||||
%Post{} = post = insert(:post, attributed_to: group)
|
%Post{} = post = insert(:post, attributed_to: group)
|
||||||
|
|
||||||
data = Convertible.model_to_as(post)
|
data = Convertible.model_to_as(post)
|
||||||
refute is_nil(Posts.get_post_by_url(data["id"]))
|
refute is_nil(Posts.get_post_by_url(data["id"]))
|
||||||
|
|
||||||
update_data = File.read!("test/fixtures/mastodon-update.json") |> Jason.decode!()
|
update_data = File.read!("test/fixtures/mastodon-update.json") |> Jason.decode!()
|
||||||
|
|
||||||
object =
|
object =
|
||||||
data
|
data
|
||||||
|> Map.put("name", "My updated post")
|
|> Map.put("name", "My updated post")
|
||||||
|> Map.put("type", "Article")
|
|> Map.put("type", "Article")
|
||||||
|
|
||||||
update_data =
|
update_data =
|
||||||
update_data
|
update_data
|
||||||
|> Map.put("actor", remote_actor_url)
|
|> Map.put("actor", remote_actor_url)
|
||||||
|> Map.put("object", object)
|
|> Map.put("object", object)
|
||||||
|
|
||||||
:error = Transmogrifier.handle_incoming(update_data)
|
:error = Transmogrifier.handle_incoming(update_data)
|
||||||
|
|
||||||
%Post{id: updated_post_id, title: updated_post_title} = Posts.get_post_by_url(data["id"])
|
%Post{id: updated_post_id, title: updated_post_title} = Posts.get_post_by_url(data["id"])
|
||||||
|
|
||||||
assert updated_post_id == post.id
|
assert updated_post_id == post.id
|
||||||
refute updated_post_title == "My updated post"
|
refute updated_post_title == "My updated post"
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -39,7 +39,7 @@ defmodule Mobilizon.Federation.ActivityPub.TransmogrifierTest do
|
||||||
Transmogrifier.handle_incoming(data)
|
Transmogrifier.handle_incoming(data)
|
||||||
|
|
||||||
assert data["id"] ==
|
assert data["id"] ==
|
||||||
"https://test.mobilizon.org/events/39026210-0c69-4238-b3cc-986f33f98ed0/activity"
|
"https://mobilizon.fr/events/39a0c4a6-f2b6-41dc-bbe2-fc5bff76cc93/activity"
|
||||||
|
|
||||||
assert data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
|
assert data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
|
||||||
|
|
||||||
|
@ -49,12 +49,12 @@ defmodule Mobilizon.Federation.ActivityPub.TransmogrifierTest do
|
||||||
# "http://localtesting.pleroma.lol/users/lain"
|
# "http://localtesting.pleroma.lol/users/lain"
|
||||||
# ]
|
# ]
|
||||||
|
|
||||||
assert data["actor"] == "https://test.mobilizon.org/@Alicia"
|
assert data["actor"] == "https://mobilizon.fr/@metacartes"
|
||||||
|
|
||||||
object = data["object"]
|
object = data["object"]
|
||||||
|
|
||||||
assert object["id"] ==
|
assert object["id"] ==
|
||||||
"https://test.mobilizon.org/events/39026210-0c69-4238-b3cc-986f33f98ed0"
|
"https://mobilizon.fr/events/39a0c4a6-f2b6-41dc-bbe2-fc5bff76cc93"
|
||||||
|
|
||||||
assert object["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
|
assert object["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
|
||||||
|
|
||||||
|
@ -63,9 +63,9 @@ defmodule Mobilizon.Federation.ActivityPub.TransmogrifierTest do
|
||||||
# "http://localtesting.pleroma.lol/users/lain"
|
# "http://localtesting.pleroma.lol/users/lain"
|
||||||
# ]
|
# ]
|
||||||
|
|
||||||
assert object["actor"] == "https://test.mobilizon.org/@Alicia"
|
assert object["actor"] == "https://mobilizon.fr/@metacartes"
|
||||||
assert object["location"]["name"] == "Locaux de Framasoft"
|
assert object["location"]["name"] == "Locaux de Framasoft"
|
||||||
# assert object["attributedTo"] == "https://test.mobilizon.org/@Alicia"
|
# assert object["attributedTo"] == "https://mobilizon.fr/@metacartes"
|
||||||
|
|
||||||
assert event.physical_address.street == "10 Rue Jangot"
|
assert event.physical_address.street == "10 Rue Jangot"
|
||||||
|
|
||||||
|
@ -84,8 +84,8 @@ defmodule Mobilizon.Federation.ActivityPub.TransmogrifierTest do
|
||||||
%Actor{url: actor_url, id: actor_id} =
|
%Actor{url: actor_url, id: actor_id} =
|
||||||
actor =
|
actor =
|
||||||
insert(:actor,
|
insert(:actor,
|
||||||
domain: "test.mobilizon.org",
|
domain: "mobilizon.fr",
|
||||||
url: "https://test.mobilizon.org/@member",
|
url: "https://mobilizon.fr/@member",
|
||||||
preferred_username: "member"
|
preferred_username: "member"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
16
test/fixtures/mastodon-update.json
vendored
16
test/fixtures/mastodon-update.json
vendored
|
@ -20,15 +20,15 @@
|
||||||
"endpoints": {
|
"endpoints": {
|
||||||
"sharedInbox": "https://framapiaf.org/inbox"
|
"sharedInbox": "https://framapiaf.org/inbox"
|
||||||
},
|
},
|
||||||
"icon":{
|
"icon": {
|
||||||
"type":"Image",
|
"type": "Image",
|
||||||
"mediaType":"image/png",
|
"mediaType": "image/png",
|
||||||
"url":"https://files.mastodon.social/accounts/avatars/000/000/001/original/a285c086605e4182.png"
|
"url": "https://files.mastodon.social/accounts/avatars/000/000/001/original/d96d39a0abb45b92.jpg"
|
||||||
},
|
},
|
||||||
"image":{
|
"image": {
|
||||||
"type":"Image",
|
"type": "Image",
|
||||||
"mediaType":"image/png",
|
"mediaType": "image/png",
|
||||||
"url":"https://files.mastodon.social/accounts/headers/000/000/001/original/c91b871f294ea63e.png"
|
"url": "https://files.mastodon.social/accounts/headers/000/000/001/original/c91b871f294ea63e.png"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"id": "https://framapiaf.org/users/gargron#updates/1519563538",
|
"id": "https://framapiaf.org/users/gargron#updates/1519563538",
|
||||||
|
|
172
test/fixtures/mobilizon-post-activity.json
vendored
172
test/fixtures/mobilizon-post-activity.json
vendored
|
@ -1,96 +1,92 @@
|
||||||
{
|
{
|
||||||
"@context": [
|
"@context": [
|
||||||
"https://www.w3.org/ns/activitystreams",
|
"https://www.w3.org/ns/activitystreams",
|
||||||
"https://litepub.social/litepub/context.jsonld",
|
"https://litepub.social/litepub/context.jsonld",
|
||||||
{
|
{
|
||||||
"Hashtag": "as:Hashtag",
|
"Hashtag": "as:Hashtag",
|
||||||
"category": "sc:category",
|
"category": "sc:category",
|
||||||
"ical": "http://www.w3.org/2002/12/cal/ical#",
|
"ical": "http://www.w3.org/2002/12/cal/ical#",
|
||||||
"joinMode": {
|
"joinMode": {
|
||||||
"@id": "mz:joinMode",
|
"@id": "mz:joinMode",
|
||||||
"@type": "mz:joinModeType"
|
"@type": "mz:joinModeType"
|
||||||
},
|
},
|
||||||
"joinModeType": {
|
"joinModeType": {
|
||||||
"@id": "mz:joinModeType",
|
"@id": "mz:joinModeType",
|
||||||
"@type": "rdfs:Class"
|
"@type": "rdfs:Class"
|
||||||
},
|
},
|
||||||
"maximumAttendeeCapacity": "sc:maximumAttendeeCapacity",
|
"maximumAttendeeCapacity": "sc:maximumAttendeeCapacity",
|
||||||
"mz": "https://joinmobilizon.org/ns#",
|
"mz": "https://joinmobilizon.org/ns#",
|
||||||
"repliesModerationOption": {
|
"repliesModerationOption": {
|
||||||
"@id": "mz:repliesModerationOption",
|
"@id": "mz:repliesModerationOption",
|
||||||
"@type": "mz:repliesModerationOptionType"
|
"@type": "mz:repliesModerationOptionType"
|
||||||
},
|
},
|
||||||
"repliesModerationOptionType": {
|
"repliesModerationOptionType": {
|
||||||
"@id": "mz:repliesModerationOptionType",
|
"@id": "mz:repliesModerationOptionType",
|
||||||
"@type": "rdfs:Class"
|
"@type": "rdfs:Class"
|
||||||
},
|
},
|
||||||
"sc": "http://schema.org#",
|
"sc": "http://schema.org#",
|
||||||
"uuid": "sc:identifier"
|
"uuid": "sc:identifier"
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"actor": "https://mobilizon.fr/@metacartes",
|
||||||
|
"cc": [
|
||||||
|
"https://framapiaf.org/users/admin/followers",
|
||||||
|
"https://framapiaf.org/users/tcit"
|
||||||
|
],
|
||||||
|
"id": "https://mobilizon.fr/events/39a0c4a6-f2b6-41dc-bbe2-fc5bff76cc93/activity",
|
||||||
|
"object": {
|
||||||
|
"attachment": [
|
||||||
|
{
|
||||||
|
"href": "https://something.org",
|
||||||
|
"mediaType": "text/html",
|
||||||
|
"name": "Another link",
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"href": "https://google.com",
|
||||||
|
"mediaType": "text/html",
|
||||||
|
"name": "Website",
|
||||||
|
"type": "Link"
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"actor": "https://test.mobilizon.org/@Alicia",
|
"attributedTo": "https://mobilizon.fr/@metacartes",
|
||||||
|
"startTime": "2018-02-12T14:08:20Z",
|
||||||
"cc": [
|
"cc": [
|
||||||
"https://framapiaf.org/users/admin/followers",
|
"https://framapiaf.org/users/admin/followers",
|
||||||
"https://framapiaf.org/users/tcit"
|
"https://framapiaf.org/users/tcit"
|
||||||
],
|
],
|
||||||
"id": "https://test.mobilizon.org/events/39026210-0c69-4238-b3cc-986f33f98ed0/activity",
|
"content": "<p><span class=\"h-card\"><a href=\"https://framapiaf.org/users/tcit\" class=\"u-url mention\">@<span>tcit</span></a></span></p>",
|
||||||
"object": {
|
"category": "TODO remove me",
|
||||||
"attachment": [
|
"id": "https://mobilizon.fr/events/39a0c4a6-f2b6-41dc-bbe2-fc5bff76cc93",
|
||||||
{
|
"inReplyTo": null,
|
||||||
"href": "https://something.org",
|
"location": {
|
||||||
"mediaType": "text/html",
|
"type": "Place",
|
||||||
"name": "Another link",
|
"name": "Locaux de Framasoft",
|
||||||
"type": "Link"
|
"id": "https://event1.tcit.fr/address/eeecc11d-0030-43e8-a897-6422876372jd",
|
||||||
},
|
"address": {
|
||||||
{
|
"type": "PostalAddress",
|
||||||
"href": "https://google.com",
|
"streetAddress": "10 Rue Jangot",
|
||||||
"mediaType": "text/html",
|
"postalCode": "69007",
|
||||||
"name": "Website",
|
"addressLocality": "Lyon",
|
||||||
"type": "Link"
|
"addressRegion": "Auvergne Rhône Alpes",
|
||||||
}
|
"addressCountry": "France"
|
||||||
],
|
}
|
||||||
"attributedTo": "https://test.mobilizon.org/@Alicia",
|
|
||||||
"startTime": "2018-02-12T14:08:20Z",
|
|
||||||
"cc": [
|
|
||||||
"https://framapiaf.org/users/admin/followers",
|
|
||||||
"https://framapiaf.org/users/tcit"
|
|
||||||
],
|
|
||||||
"content": "<p><span class=\"h-card\"><a href=\"https://framapiaf.org/users/tcit\" class=\"u-url mention\">@<span>tcit</span></a></span></p>",
|
|
||||||
"category": "TODO remove me",
|
|
||||||
"id": "https://test.mobilizon.org/events/39026210-0c69-4238-b3cc-986f33f98ed0",
|
|
||||||
"inReplyTo": null,
|
|
||||||
"location": {
|
|
||||||
"type": "Place",
|
|
||||||
"name": "Locaux de Framasoft",
|
|
||||||
"id": "https://event1.tcit.fr/address/eeecc11d-0030-43e8-a897-6422876372jd",
|
|
||||||
"address": {
|
|
||||||
"type": "PostalAddress",
|
|
||||||
"streetAddress": "10 Rue Jangot",
|
|
||||||
"postalCode": "69007",
|
|
||||||
"addressLocality": "Lyon",
|
|
||||||
"addressRegion": "Auvergne Rhône Alpes",
|
|
||||||
"addressCountry": "France"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"name": "My first event",
|
|
||||||
"published": "2018-02-12T14:08:20Z",
|
|
||||||
"tag": [
|
|
||||||
{
|
|
||||||
"href": "https://framapiaf.org/users/tcit",
|
|
||||||
"name": "@tcit@framapiaf.org",
|
|
||||||
"type": "Mention"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"to": [
|
|
||||||
"https://www.w3.org/ns/activitystreams#Public"
|
|
||||||
],
|
|
||||||
"type": "Event",
|
|
||||||
"url": "https://test.mobilizon.org/events/39026210-0c69-4238-b3cc-986f33f98ed0",
|
|
||||||
"uuid": "109ccdfd-ee3e-46e1-a877-6c228763df0c"
|
|
||||||
},
|
},
|
||||||
|
"name": "My first event",
|
||||||
"published": "2018-02-12T14:08:20Z",
|
"published": "2018-02-12T14:08:20Z",
|
||||||
"to": [
|
"tag": [
|
||||||
"https://www.w3.org/ns/activitystreams#Public"
|
{
|
||||||
|
"href": "https://framapiaf.org/users/tcit",
|
||||||
|
"name": "@tcit@framapiaf.org",
|
||||||
|
"type": "Mention"
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"type": "Create"
|
"to": ["https://www.w3.org/ns/activitystreams#Public"],
|
||||||
|
"type": "Event",
|
||||||
|
"url": "https://mobilizon.fr/events/39a0c4a6-f2b6-41dc-bbe2-fc5bff76cc93",
|
||||||
|
"uuid": "109ccdfd-ee3e-46e1-a877-6c228763df0c"
|
||||||
|
},
|
||||||
|
"published": "2018-02-12T14:08:20Z",
|
||||||
|
"to": ["https://www.w3.org/ns/activitystreams#Public"],
|
||||||
|
"type": "Create"
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,24 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -108,7 +108,7 @@ defmodule Mobilizon.ActorsTest do
|
||||||
avatar: %FileModel{name: picture_name} = _picture
|
avatar: %FileModel{name: picture_name} = _picture
|
||||||
} = _actor} = ActivityPub.get_or_fetch_actor_by_url(@remote_account_url)
|
} = _actor} = ActivityPub.get_or_fetch_actor_by_url(@remote_account_url)
|
||||||
|
|
||||||
assert picture_name == "avatar"
|
assert picture_name == "a28c50ce5f2b13fd.jpg"
|
||||||
|
|
||||||
%Actor{
|
%Actor{
|
||||||
id: actor_found_id,
|
id: actor_found_id,
|
||||||
|
@ -116,7 +116,7 @@ defmodule Mobilizon.ActorsTest do
|
||||||
} = Actors.get_actor_by_name("#{preferred_username}@#{domain}")
|
} = Actors.get_actor_by_name("#{preferred_username}@#{domain}")
|
||||||
|
|
||||||
assert actor_found_id == actor_id
|
assert actor_found_id == actor_id
|
||||||
assert picture_name == "avatar"
|
assert picture_name == "a28c50ce5f2b13fd.jpg"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -374,6 +374,7 @@ defmodule Mobilizon.Factory do
|
||||||
tags: build_list(3, :tag),
|
tags: build_list(3, :tag),
|
||||||
visibility: :public,
|
visibility: :public,
|
||||||
publish_at: DateTime.utc_now(),
|
publish_at: DateTime.utc_now(),
|
||||||
|
picture: insert(:media),
|
||||||
media: [],
|
media: [],
|
||||||
url: Routes.page_url(Endpoint, :post, uuid)
|
url: Routes.page_url(Endpoint, :post, uuid)
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,7 +52,7 @@ defmodule Mobilizon.Web.Plugs.MappedSignatureToIdentityTest do
|
||||||
use_cassette "activity_pub/signature/invalid_not_found" do
|
use_cassette "activity_pub/signature/invalid_not_found" do
|
||||||
conn =
|
conn =
|
||||||
build_conn(:post, "/doesntmattter", %{"actor" => "https://framapiaf.org/users/admin"})
|
build_conn(:post, "/doesntmattter", %{"actor" => "https://framapiaf.org/users/admin"})
|
||||||
|> set_signature("http://niu.moe/users/rye")
|
|> set_signature("https://mastodon.social/users/gargron")
|
||||||
|> MappedSignatureToIdentity.call(%{})
|
|> MappedSignatureToIdentity.call(%{})
|
||||||
|
|
||||||
assert %{valid_signature: false} == conn.assigns
|
assert %{valid_signature: false} == conn.assigns
|
||||||
|
|
|
@ -1,185 +0,0 @@
|
||||||
# Portions of this file are derived from Pleroma:
|
|
||||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
# Upstream: https://git.pleroma.social/pleroma/pleroma/blob/develop/test/media_proxy_test.ex
|
|
||||||
|
|
||||||
defmodule Mobilizon.Web.MediaProxyTest do
|
|
||||||
use ExUnit.Case
|
|
||||||
|
|
||||||
import Mobilizon.Web.MediaProxy
|
|
||||||
|
|
||||||
alias Mobilizon.Config
|
|
||||||
|
|
||||||
alias Mobilizon.Web.{Endpoint, MediaProxyController}
|
|
||||||
|
|
||||||
setup do
|
|
||||||
enabled = Config.get([:media_proxy, :enabled])
|
|
||||||
on_exit(fn -> Config.put([:media_proxy, :enabled], enabled) end)
|
|
||||||
:ok
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "when enabled" do
|
|
||||||
setup do
|
|
||||||
Config.put([:media_proxy, :enabled], true)
|
|
||||||
:ok
|
|
||||||
end
|
|
||||||
|
|
||||||
test "ignores invalid url" do
|
|
||||||
assert url(nil) == nil
|
|
||||||
assert url("") == nil
|
|
||||||
end
|
|
||||||
|
|
||||||
test "ignores relative url" do
|
|
||||||
assert url("/local") == "/local"
|
|
||||||
assert url("/") == "/"
|
|
||||||
end
|
|
||||||
|
|
||||||
test "ignores local url" do
|
|
||||||
local_url = Endpoint.url() <> "/hello"
|
|
||||||
local_root = Endpoint.url()
|
|
||||||
assert url(local_url) == local_url
|
|
||||||
assert url(local_root) == local_root
|
|
||||||
end
|
|
||||||
|
|
||||||
test "encodes and decodes URL" do
|
|
||||||
url = "https://pleroma.soykaf.com/static/logo.png"
|
|
||||||
encoded = url(url)
|
|
||||||
|
|
||||||
assert String.starts_with?(
|
|
||||||
encoded,
|
|
||||||
Config.get([:media_proxy, :base_url], Endpoint.url())
|
|
||||||
)
|
|
||||||
|
|
||||||
assert String.ends_with?(encoded, "/logo.png")
|
|
||||||
|
|
||||||
assert decode_result(encoded) == url
|
|
||||||
end
|
|
||||||
|
|
||||||
test "encodes and decodes URL without a path" do
|
|
||||||
url = "https://pleroma.soykaf.com"
|
|
||||||
encoded = url(url)
|
|
||||||
assert decode_result(encoded) == url
|
|
||||||
end
|
|
||||||
|
|
||||||
test "encodes and decodes URL without an extension" do
|
|
||||||
url = "https://pleroma.soykaf.com/path/"
|
|
||||||
encoded = url(url)
|
|
||||||
assert String.ends_with?(encoded, "/path")
|
|
||||||
assert decode_result(encoded) == url
|
|
||||||
end
|
|
||||||
|
|
||||||
test "encodes and decodes URL and ignores query params for the path" do
|
|
||||||
url = "https://pleroma.soykaf.com/static/logo.png?93939393939&bunny=true"
|
|
||||||
encoded = url(url)
|
|
||||||
assert String.ends_with?(encoded, "/logo.png")
|
|
||||||
assert decode_result(encoded) == url
|
|
||||||
end
|
|
||||||
|
|
||||||
test "ensures urls are url-encoded" do
|
|
||||||
assert decode_result(url("https://pleroma.social/Hello world.jpg")) ==
|
|
||||||
"https://pleroma.social/Hello%20world.jpg"
|
|
||||||
|
|
||||||
assert decode_result(url("https://pleroma.social/Hello%20world.jpg")) ==
|
|
||||||
"https://pleroma.social/Hello%20world.jpg"
|
|
||||||
end
|
|
||||||
|
|
||||||
test "validates signature" do
|
|
||||||
secret_key_base = Config.get([Endpoint, :secret_key_base])
|
|
||||||
|
|
||||||
on_exit(fn ->
|
|
||||||
Config.put([Endpoint, :secret_key_base], secret_key_base)
|
|
||||||
end)
|
|
||||||
|
|
||||||
encoded = url("https://pleroma.social")
|
|
||||||
|
|
||||||
Config.put(
|
|
||||||
[Endpoint, :secret_key_base],
|
|
||||||
"00000000000000000000000000000000000000000000000"
|
|
||||||
)
|
|
||||||
|
|
||||||
[_, "proxy", sig, base64 | _] = URI.parse(encoded).path |> String.split("/")
|
|
||||||
assert decode_url(sig, base64) == {:error, :invalid_signature}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "filename_matches matches url encoded paths" do
|
|
||||||
assert MediaProxyController.filename_matches(
|
|
||||||
true,
|
|
||||||
"/Hello%20world.jpg",
|
|
||||||
"http://pleroma.social/Hello world.jpg"
|
|
||||||
) == :ok
|
|
||||||
|
|
||||||
assert MediaProxyController.filename_matches(
|
|
||||||
true,
|
|
||||||
"/Hello%20world.jpg",
|
|
||||||
"http://pleroma.social/Hello%20world.jpg"
|
|
||||||
) == :ok
|
|
||||||
end
|
|
||||||
|
|
||||||
test "filename_matches matches non-url encoded paths" do
|
|
||||||
assert MediaProxyController.filename_matches(
|
|
||||||
true,
|
|
||||||
"/Hello world.jpg",
|
|
||||||
"http://pleroma.social/Hello%20world.jpg"
|
|
||||||
) == :ok
|
|
||||||
|
|
||||||
assert MediaProxyController.filename_matches(
|
|
||||||
true,
|
|
||||||
"/Hello world.jpg",
|
|
||||||
"http://pleroma.social/Hello world.jpg"
|
|
||||||
) == :ok
|
|
||||||
end
|
|
||||||
|
|
||||||
test "uses the configured base_url" do
|
|
||||||
base_url = Config.get([:media_proxy, :base_url])
|
|
||||||
|
|
||||||
if base_url do
|
|
||||||
on_exit(fn ->
|
|
||||||
Config.put([:media_proxy, :base_url], base_url)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
Config.put([:media_proxy, :base_url], "https://cache.pleroma.social")
|
|
||||||
|
|
||||||
url = "https://pleroma.soykaf.com/static/logo.png"
|
|
||||||
encoded = url(url)
|
|
||||||
|
|
||||||
assert String.starts_with?(encoded, Config.get([:media_proxy, :base_url]))
|
|
||||||
end
|
|
||||||
|
|
||||||
# https://git.pleroma.social/pleroma/pleroma/issues/580
|
|
||||||
test "encoding S3 links (must preserve `%2F`)" do
|
|
||||||
url =
|
|
||||||
"https://s3.amazonaws.com/example/test.png?X-Amz-Credential=your-access-key-id%2F20130721%2Fus-east-1%2Fs3%2Faws4_request"
|
|
||||||
|
|
||||||
encoded = url(url)
|
|
||||||
assert decode_result(encoded) == url
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "when disabled" do
|
|
||||||
setup do
|
|
||||||
enabled = Config.get([:media_proxy, :enabled])
|
|
||||||
|
|
||||||
if enabled do
|
|
||||||
Config.put([:media_proxy, :enabled], false)
|
|
||||||
|
|
||||||
on_exit(fn ->
|
|
||||||
Config.put([:media_proxy, :enabled], enabled)
|
|
||||||
:ok
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
:ok
|
|
||||||
end
|
|
||||||
|
|
||||||
test "does not encode remote urls" do
|
|
||||||
assert url("https://google.fr") == "https://google.fr"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp decode_result(encoded) do
|
|
||||||
[_, "proxy", sig, base64 | _] = URI.parse(encoded).path |> String.split("/")
|
|
||||||
{:ok, decoded} = decode_url(sig, base64)
|
|
||||||
decoded
|
|
||||||
end
|
|
||||||
end
|
|
Loading…
Reference in a new issue