From 3f71ddfe5b684ff6baab62d85e433d59ae076f61 Mon Sep 17 00:00:00 2001 From: Vincent Date: Tue, 16 Apr 2019 12:25:18 +0200 Subject: [PATCH 1/4] Move eventos to mobilizon --- lib/{eventos.ex => mobilizon.ex} | 0 lib/{eventos_web.ex => mobilizon_web.ex} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename lib/{eventos.ex => mobilizon.ex} (100%) rename lib/{eventos_web.ex => mobilizon_web.ex} (100%) diff --git a/lib/eventos.ex b/lib/mobilizon.ex similarity index 100% rename from lib/eventos.ex rename to lib/mobilizon.ex diff --git a/lib/eventos_web.ex b/lib/mobilizon_web.ex similarity index 100% rename from lib/eventos_web.ex rename to lib/mobilizon_web.ex From fb0e9c42f83156c8e77deba971fe10494e57c004 Mon Sep 17 00:00:00 2001 From: Vincent Date: Thu, 2 May 2019 11:38:57 +0200 Subject: [PATCH 2/4] Add argon2 dependency in Docker --- Dockerfile | 2 +- mix.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 029e6a58e..8685ad386 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ FROM bitwalker/alpine-elixir:latest RUN apk add inotify-tools postgresql-client yarn -RUN apk add --no-cache make gcc libc-dev +RUN apk add --no-cache make gcc libc-dev argon2 RUN mix local.hex --force && mix local.rebar --force diff --git a/mix.lock b/mix.lock index 79d42914d..5d997c15c 100644 --- a/mix.lock +++ b/mix.lock @@ -5,7 +5,7 @@ "absinthe_plug": {:hex, :absinthe_plug, "1.4.6", "ac5d2d3d02acf52fda0f151b294017ab06e2ed1c6c15334e06aac82c94e36e08", [:mix], [{:absinthe, "~> 1.4.11", [hex: :absinthe, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.2 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, "arc": {:hex, :arc, "0.11.0", "ac7a0cc03035317b6fef9fe94c97d7d9bd183a3e7ce1606aa0c175cfa8d1ba6d", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:ex_aws_s3, "~> 2.0", [hex: :ex_aws_s3, repo: "hexpm", optional: true]}, {:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:poison, "~> 2.2 or ~> 3.1", [hex: :poison, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm"}, "arc_ecto": {:hex, :arc_ecto, "0.11.1", "27aedf8c236b2097eed09d96f4ae73b43eb4c042a0e2ae42d44bf644cf16115c", [:mix], [{:arc, "~> 0.11.0", [hex: :arc, repo: "hexpm", optional: false]}, {:ecto, "~> 2.1 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}], "hexpm"}, - "argon2_elixir": {:hex, :argon2_elixir, "2.0.2", "71c0752ad38e0903140f466b465eb6f4278b16aef18ca5410d1a7cfc04a50aca", [:make, :mix], [{:comeonin, "~> 5.1", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"}, + "argon2_elixir": {:hex, :argon2_elixir, "2.0.3", "f2272c89d6a84f85c22b9b83912fd60740bf6052bf0078e621a6e9d1127e25c8", [:make, :mix], [{:comeonin, "~> 5.1", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.5", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"}, "atomex": {:hex, :atomex, "0.3.0", "19b5d1a2aef8706dbd307385f7d5d9f6f273869226d317492c396c7bacf26402", [:mix], [{:xml_builder, "~> 2.0.0", [hex: :xml_builder, repo: "hexpm", optional: false]}], "hexpm"}, "bamboo": {:hex, :bamboo, "1.2.0", "8aebd24f7c606c32d0163c398004a11608ca1028182a169b2e527793bfab7561", [:mix], [{:hackney, ">= 1.13.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, ">= 1.5.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"}, "bamboo_smtp": {:hex, :bamboo_smtp, "1.6.0", "0a3607b77f22554af58c547350c1c73ebba6f4fb2c4bd0b11713ab5b4081588f", [:mix], [{:bamboo, "~> 1.0", [hex: :bamboo, repo: "hexpm", optional: false]}, {:gen_smtp, "~> 0.12.0", [hex: :gen_smtp, repo: "hexpm", optional: false]}], "hexpm"}, From 7f311218803fdeadb3db425f3b1153855a394c56 Mon Sep 17 00:00:00 2001 From: Vincent Date: Thu, 2 May 2019 13:04:21 +0200 Subject: [PATCH 3/4] Simplify PageController --- config/config.exs | 2 +- .../controllers/activity_pub_controller.ex | 68 ---------------- .../controllers/fallback_controller.ex | 2 +- .../controllers/page_controller.ex | 72 +++++------------ .../views/activity_pub/object_view.ex | 39 --------- lib/mobilizon_web/views/error_view.ex | 4 + lib/mobilizon_web/views/page_view.ex | 79 +++++++++++++++++++ .../activity_pub_controller_test.exs | 7 +- 8 files changed, 109 insertions(+), 164 deletions(-) diff --git a/config/config.exs b/config/config.exs index 1f26daec3..c83cac04c 100644 --- a/config/config.exs +++ b/config/config.exs @@ -64,7 +64,7 @@ config :geolix, config :arc, storage: Arc.Storage.Local -config :phoenix, :format_encoders, json: Jason +config :phoenix, :format_encoders, json: Jason, "activity-json": Jason config :mobilizon, Mobilizon.Service.Geospatial.Nominatim, endpoint: diff --git a/lib/mobilizon_web/controllers/activity_pub_controller.ex b/lib/mobilizon_web/controllers/activity_pub_controller.ex index 4ffae9395..e0898745a 100644 --- a/lib/mobilizon_web/controllers/activity_pub_controller.ex +++ b/lib/mobilizon_web/controllers/activity_pub_controller.ex @@ -16,74 +16,6 @@ defmodule MobilizonWeb.ActivityPubController do action_fallback(:errors) - @doc """ - Renders an Actor ActivityPub's representation - """ - @spec actor(Plug.Conn.t(), String.t()) :: Plug.Conn.t() - def actor(conn, %{"name" => name}) do - with {status, %Actor{} = actor} when status in [:ok, :commit] <- - Actors.get_cached_local_actor_by_name(name) do - conn - |> put_resp_header("content-type", "application/activity+json") - |> json(ActorView.render("actor.json", %{actor: actor})) - else - {:ignore, _} -> - {:error, :not_found} - end - end - - @doc """ - Renders an Event ActivityPub's representation - """ - @spec event(Plug.Conn.t(), map()) :: Plug.Conn.t() - def event(conn, %{"uuid" => uuid}) do - with {status, %Event{} = event} when status in [:ok, :commit] <- - Events.get_cached_event_full_by_uuid(uuid), - true <- event.visibility in [:public, :unlisted] do - conn - |> put_resp_header("content-type", "application/activity+json") - |> json( - ObjectView.render( - "event.json", - %{ - event: - event - |> Utils.make_event_data() - } - ) - ) - else - {:ignore, _} -> - {:error, :not_found} - end - end - - @doc """ - Renders a Comment ActivityPub's representation - """ - @spec comment(Plug.Conn.t(), map()) :: Plug.Conn.t() - def comment(conn, %{"uuid" => uuid}) do - with {status, %Comment{} = comment} when status in [:ok, :commit] <- - Events.get_cached_comment_full_by_uuid(uuid), - true <- comment.visibility in [:public, :unlisted] do - conn - |> put_resp_header("content-type", "application/activity+json") - |> json( - ObjectView.render( - "comment.json", - %{ - comment: - comment - |> Utils.make_comment_data() - } - ) - ) - else - _ -> - {:error, :not_found} - end - end - def following(conn, %{"name" => name, "page" => page}) do with {page, ""} = Integer.parse(page), %Actor{} = actor <- Actors.get_local_actor_by_name_with_everything(name) do diff --git a/lib/mobilizon_web/controllers/fallback_controller.ex b/lib/mobilizon_web/controllers/fallback_controller.ex index ec74eadba..aea620e1b 100644 --- a/lib/mobilizon_web/controllers/fallback_controller.ex +++ b/lib/mobilizon_web/controllers/fallback_controller.ex @@ -10,6 +10,6 @@ defmodule MobilizonWeb.FallbackController do conn |> put_status(:not_found) |> put_view(MobilizonWeb.ErrorView) - |> render("404.html") + |> render(:"404") end end diff --git a/lib/mobilizon_web/controllers/page_controller.ex b/lib/mobilizon_web/controllers/page_controller.ex index 5bb761542..9c479edff 100644 --- a/lib/mobilizon_web/controllers/page_controller.ex +++ b/lib/mobilizon_web/controllers/page_controller.ex @@ -10,69 +10,35 @@ defmodule MobilizonWeb.PageController do action_fallback(MobilizonWeb.FallbackController) - def index(conn, _params) do - render(conn, "app.html") - end + def index(conn, _params), do: render(conn, :index) def actor(conn, %{"name" => name}) do - case get_format(conn) do - "html" -> - with {status, %Actor{} = actor} when status in [:ok, :commit] <- - Actors.get_cached_local_actor_by_name(name) do - render_with_meta(conn, actor) - else - _ -> {:error, :not_found} - end - - # "activity-json" matches "application/activity+json" inside our config - "activity-json" -> - MobilizonWeb.ActivityPubController.call(conn, :actor) - - _ -> - {:error, :not_found} - end + {status, actor} = Actors.get_cached_local_actor_by_name(name) + render_or_error(conn, &ok_status?/2, status, :actor, actor) end def event(conn, %{"uuid" => uuid}) do - case get_format(conn) do - "html" -> - with {status, %Event{} = event} when status in [:ok, :commit] <- - Events.get_cached_event_full_by_uuid(uuid), - true <- event.visibility in [:public, :unlisted] do - render_with_meta(conn, event) - else - _ -> {:error, :not_found} - end - - "activity-json" -> - MobilizonWeb.ActivityPubController.call(conn, :event) - - _ -> - {:error, :not_found} - end + {status, event} = Events.get_cached_event_full_by_uuid(uuid) + render_or_error(conn, &ok_status_and_is_visible?/2, status, :event, event) end def comment(conn, %{"uuid" => uuid}) do - case get_format(conn) do - "html" -> - with {status, %Comment{} = comment} when status in [:ok, :commit] <- - Events.get_cached_comment_full_by_uuid(uuid), - true <- comment.visibility in [:public, :unlisted] do - render_with_meta(conn, comment) - else - _ -> {:error, :not_found} - end + {status, comment} = Events.get_cached_comment_full_by_uuid(uuid) + render_or_error(conn, &ok_status_and_is_visible?/2, status, :comment, comment) + end - "activity-json" -> - MobilizonWeb.ActivityPubController.call(conn, :comment) - - _ -> - {:error, :not_found} + defp render_or_error(conn, check_fn, status, object_type, object) do + if check_fn.(status, object) do + render(conn, object_type, object: object) + else + {:error, :not_found} end end - # Inject OpenGraph information - defp render_with_meta(conn, object) do - render(conn, "app.html", object: object) - end + defp is_visible?(%{visibility: v}), do: v in [:public, :unlisted] + + defp ok_status?(status), do: status in [:ok, :commit] + defp ok_status?(status, _), do: ok_status?(status) + + defp ok_status_and_is_visible?(status, o), do: ok_status?(status) and is_visible?(o) end diff --git a/lib/mobilizon_web/views/activity_pub/object_view.ex b/lib/mobilizon_web/views/activity_pub/object_view.ex index 9e7de95b8..9a1da8fca 100644 --- a/lib/mobilizon_web/views/activity_pub/object_view.ex +++ b/lib/mobilizon_web/views/activity_pub/object_view.ex @@ -3,45 +3,6 @@ defmodule MobilizonWeb.ActivityPub.ObjectView do alias Mobilizon.Service.ActivityPub.Utils alias Mobilizon.Activity - def render("event.json", %{event: event}) do - {:ok, html, []} = Earmark.as_html(event["summary"]) - - event = %{ - "type" => "Event", - "attributedTo" => event["actor"], - "id" => event["id"], - "name" => event["title"], - "category" => event["category"], - "content" => html, - "source" => %{ - "content" => event["summary"], - "mediaType" => "text/markdown" - }, - "mediaType" => "text/html", - "published" => event["publish_at"], - "updated" => event["updated_at"] - } - - Map.merge(event, Utils.make_json_ld_header()) - end - - def render("comment.json", %{comment: comment}) do - comment = %{ - "actor" => comment["actor"], - "uuid" => comment["uuid"], - # The activity should have attributedTo, not the comment itself - # "attributedTo" => comment.attributed_to, - "type" => "Note", - "id" => comment["id"], - "content" => comment["content"], - "mediaType" => "text/html" - # "published" => Timex.format!(comment.inserted_at, "{ISO:Extended}"), - # "updated" => Timex.format!(comment.updated_at, "{ISO:Extended}") - } - - Map.merge(comment, Utils.make_json_ld_header()) - end - def render("activity.json", %{activity: %Activity{local: local, data: data} = activity}) do %{ "id" => data["id"], diff --git a/lib/mobilizon_web/views/error_view.ex b/lib/mobilizon_web/views/error_view.ex index c4d6b3ce4..3a252a8bf 100644 --- a/lib/mobilizon_web/views/error_view.ex +++ b/lib/mobilizon_web/views/error_view.ex @@ -8,6 +8,10 @@ defmodule MobilizonWeb.ErrorView do "Page not found" end + def render("404.json", _assigns) do + %{msg: "Resource not found"} + end + def render("invalid_request.json", _assigns) do %{errors: "Invalid request"} end diff --git a/lib/mobilizon_web/views/page_view.ex b/lib/mobilizon_web/views/page_view.ex index ea88b06af..0166680fd 100644 --- a/lib/mobilizon_web/views/page_view.ex +++ b/lib/mobilizon_web/views/page_view.ex @@ -3,4 +3,83 @@ defmodule MobilizonWeb.PageView do View for our webapp """ use MobilizonWeb, :view + alias Mobilizon.Actors.Actor + alias Mobilizon.Service.ActivityPub.Utils + + def render("actor.activity-json", %{conn: %{assigns: %{object: actor}}}) do + public_key = Mobilizon.Service.ActivityPub.Utils.pem_to_public_key_pem(actor.keys) + + %{ + "id" => Actor.build_url(actor.preferred_username, :page), + "type" => "Person", + "following" => Actor.build_url(actor.preferred_username, :following), + "followers" => Actor.build_url(actor.preferred_username, :followers), + "inbox" => Actor.build_url(actor.preferred_username, :inbox), + "outbox" => Actor.build_url(actor.preferred_username, :outbox), + "preferredUsername" => actor.preferred_username, + "name" => actor.name, + "summary" => actor.summary, + "url" => actor.url, + "manuallyApprovesFollowers" => actor.manually_approves_followers, + "publicKey" => %{ + "id" => "#{actor.url}#main-key", + "owner" => actor.url, + "publicKeyPem" => public_key + }, + # TODO : Make have actors have an uuid + # "uuid" => actor.uuid + "endpoints" => %{ + "sharedInbox" => actor.shared_inbox_url + } + # "icon" => %{ + # "type" => "Image", + # "url" => User.avatar_url(actor) + # }, + # "image" => %{ + # "type" => "Image", + # "url" => User.banner_url(actor) + # } + } + |> Map.merge(Utils.make_json_ld_header()) + end + + def render("event.activity-json", %{conn: %{assigns: %{object: event}}}) do + event = Utils.make_event_data(event) + {:ok, html, []} = Earmark.as_html(event["summary"]) + + %{ + "type" => "Event", + "attributedTo" => event["actor"], + "id" => event["id"], + "name" => event["title"], + "category" => event["category"], + "content" => html, + "source" => %{ + "content" => event["summary"], + "mediaType" => "text/markdown" + }, + "mediaType" => "text/html", + "published" => event["publish_at"], + "updated" => event["updated_at"] + } + |> Map.merge(Utils.make_json_ld_header()) + end + + def render("comment.activity-json", %{conn: %{assigns: %{object: comment}}}) do + comment = Utils.make_comment_data(comment) + + %{ + "actor" => comment["actor"], + "uuid" => comment["uuid"], + # The activity should have attributedTo, not the comment itself + # "attributedTo" => comment.attributed_to, + "type" => "Note", + "id" => comment["id"], + "content" => comment["content"], + "mediaType" => "text/html" + # "published" => Timex.format!(comment.inserted_at, "{ISO:Extended}"), + # "updated" => Timex.format!(comment.updated_at, "{ISO:Extended}") + } + |> Map.merge(Utils.make_json_ld_header()) + end end diff --git a/test/mobilizon_web/controllers/activity_pub_controller_test.exs b/test/mobilizon_web/controllers/activity_pub_controller_test.exs index b5217964c..3b0eef813 100644 --- a/test/mobilizon_web/controllers/activity_pub_controller_test.exs +++ b/test/mobilizon_web/controllers/activity_pub_controller_test.exs @@ -7,6 +7,7 @@ defmodule MobilizonWeb.ActivityPubControllerTest do use MobilizonWeb.ConnCase import Mobilizon.Factory alias MobilizonWeb.ActivityPub.{ActorView, ObjectView} + alias MobilizonWeb.PageView alias Mobilizon.Actors alias Mobilizon.Actors.Actor alias Mobilizon.Service.ActivityPub @@ -43,7 +44,7 @@ defmodule MobilizonWeb.ActivityPubControllerTest do |> get(Routes.page_url(Endpoint, :event, event.uuid)) assert json_response(conn, 200) == - ObjectView.render("event.json", %{event: event |> Utils.make_event_data()}) + PageView.render("event.activity-json", %{conn: %{assigns: %{object: event}}}) end test "it returns 404 for non-public events", %{conn: conn} do @@ -51,6 +52,7 @@ defmodule MobilizonWeb.ActivityPubControllerTest do conn = conn + |> put_req_header("accept", "application/activity+json") |> get(Routes.page_url(Endpoint, :event, event.uuid)) assert json_response(conn, 404) @@ -63,10 +65,11 @@ defmodule MobilizonWeb.ActivityPubControllerTest do conn = conn + |> put_req_header("accept", "application/activity+json") |> get(Routes.page_url(Endpoint, :comment, comment.uuid)) assert json_response(conn, 200) == - ObjectView.render("comment.json", %{comment: comment |> Utils.make_comment_data()}) + PageView.render("comment.activity-json", %{conn: %{assigns: %{object: comment}}}) end test "it returns 404 for non-public comments", %{conn: conn} do From f07216bbd7eecb42c83518fed9fe89d66024b47f Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Thu, 2 May 2019 13:53:19 +0200 Subject: [PATCH 4/4] Assert HTML contains opengraph content Signed-off-by: Thomas Citharel --- test/mobilizon_web/controllers/page_controller_test.exs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/mobilizon_web/controllers/page_controller_test.exs b/test/mobilizon_web/controllers/page_controller_test.exs index a7c871bb8..477a11d25 100644 --- a/test/mobilizon_web/controllers/page_controller_test.exs +++ b/test/mobilizon_web/controllers/page_controller_test.exs @@ -18,7 +18,7 @@ defmodule MobilizonWeb.PageControllerTest do test "GET /@actor with existing actor", %{conn: conn} do actor = insert(:actor) conn = get(conn, Actor.build_url(actor.preferred_username, :page)) - assert html_response(conn, 200) + assert html_response(conn, 200) =~ actor.preferred_username end test "GET /@actor with not existing actor", %{conn: conn} do @@ -29,7 +29,7 @@ defmodule MobilizonWeb.PageControllerTest do test "GET /events/:uuid", %{conn: conn} do event = insert(:event) conn = get(conn, Routes.page_url(Endpoint, :event, event.uuid)) - assert html_response(conn, 200) + assert html_response(conn, 200) =~ event.title end test "GET /events/:uuid with not existing event", %{conn: conn} do @@ -46,7 +46,7 @@ defmodule MobilizonWeb.PageControllerTest do test "GET /comments/:uuid", %{conn: conn} do comment = insert(:comment) conn = get(conn, Routes.page_url(Endpoint, :comment, comment.uuid)) - assert html_response(conn, 200) + assert html_response(conn, 200) =~ comment.text end test "GET /comments/:uuid with not existing comment", %{conn: conn} do