From: kaniini Date: Thu, 6 Dec 2018 15:57:56 +0000 (+0000) Subject: Merge branch 'correct-and-improve-http-options' into 'develop' X-Git-Url: http://git.squeep.com/?a=commitdiff_plain;h=abead01ab6f5586e60d467d7efd0de560a97c5bd;hp=a09ed0f5afe6336763b47edab0c0d9a629a74ad0;p=akkoma Merge branch 'correct-and-improve-http-options' into 'develop' Correct and improve http options See merge request pleroma/pleroma!505 --- diff --git a/config/config.md b/config/config.md index 47e838dd6..d90d18566 100644 --- a/config/config.md +++ b/config/config.md @@ -39,6 +39,7 @@ Note: `strip_exif` has been replaced by `Pleroma.Upload.Filter.Mogrify`. * `Pleroma.Web.ActivityPub.MRF.DropPolicy`: Drops all activities. It generally doesn’t makes sense to use in production * `Pleroma.Web.ActivityPub.MRF.SimplePolicy`: Restrict the visibility of activities from certains instances (See ``:mrf_simple`` section) * `Pleroma.Web.ActivityPub.MRF.RejectNonPublic`: Drops posts with non-public visibility settings (See ``:mrf_rejectnonpublic`` section) + * `Pleroma.Web.ActivityPub.MRF.EnsureRePrepended`: Rewrites posts to ensure that replies to posts with subjects do not have an identical subject and instead begin with re:. * `public`: Makes the client API in authentificated mode-only except for user-profiles. Useful for disabling the Local Timeline and The Whole Known Network. * `quarantined_instances`: List of ActivityPub instances where private(DMs, followers-only) activities will not be send. * `managed_config`: Whenether the config for pleroma-fe is configured in this config or in ``static/config.json`` diff --git a/config/test.exs b/config/test.exs index 6f6227c20..ca10a616c 100644 --- a/config/test.exs +++ b/config/test.exs @@ -27,6 +27,12 @@ config :pleroma, :websub, Pleroma.Web.WebsubMock config :pleroma, :ostatus, Pleroma.Web.OStatusMock config :tesla, adapter: Tesla.Mock +config :web_push_encryption, :vapid_details, + subject: "mailto:administrator@example.com", + public_key: + "BLH1qVhJItRGCfxgTtONfsOKDc9VRAraXw-3NsmjMngWSh7NxOizN6bkuRA7iLTMPS82PjwJAr3UoK9EC1IFrz4", + private_key: "_-XZ0iebPrRfZ_o0-IatTdszYa8VCH1yLN-JauK7HHA" + try do import_config "test.secret.exs" rescue diff --git a/lib/mix/tasks/generate_config.ex b/lib/mix/tasks/generate_config.ex index e3cbbf131..be085d187 100644 --- a/lib/mix/tasks/generate_config.ex +++ b/lib/mix/tasks/generate_config.ex @@ -22,6 +22,8 @@ defmodule Mix.Tasks.GenerateConfig do resultSql = EEx.eval_file("lib/mix/tasks/sample_psql.eex", dbpass: dbpass) + {web_push_public_key, web_push_private_key} = :crypto.generate_key(:ecdh, :prime256v1) + result = EEx.eval_file( "lib/mix/tasks/sample_config.eex", @@ -29,7 +31,9 @@ defmodule Mix.Tasks.GenerateConfig do email: email, name: name, secret: secret, - dbpass: dbpass + dbpass: dbpass, + web_push_public_key: Base.url_encode64(web_push_public_key, padding: false), + web_push_private_key: Base.url_encode64(web_push_private_key, padding: false) ) IO.puts( diff --git a/lib/mix/tasks/sample_config.eex b/lib/mix/tasks/sample_config.eex index 462c34636..47b6be729 100644 --- a/lib/mix/tasks/sample_config.eex +++ b/lib/mix/tasks/sample_config.eex @@ -25,6 +25,12 @@ config :pleroma, Pleroma.Repo, hostname: "localhost", pool_size: 10 +# Configure web push notifications +config :web_push_encryption, :vapid_details, + subject: "mailto:<%= email %>", + public_key: "<%= web_push_public_key %>", + private_key: "<%= web_push_private_key %>" + # Enable Strict-Transport-Security once SSL is working: # config :pleroma, :http_security, # sts: true @@ -50,9 +56,9 @@ config :pleroma, Pleroma.Repo, # Configure Openstack Swift support if desired. -# -# Many openstack deployments are different, so config is left very open with -# no assumptions made on which provider you're using. This should allow very +# +# Many openstack deployments are different, so config is left very open with +# no assumptions made on which provider you're using. This should allow very # wide support without needing separate handlers for OVH, Rackspace, etc. # # config :pleroma, Pleroma.Uploaders.Swift, diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index cc68d9669..0b0ec0197 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -66,7 +66,8 @@ defmodule Pleroma.Application do ), worker(Pleroma.Web.Federator.RetryQueue, []), worker(Pleroma.Web.Federator, []), - worker(Pleroma.Stats, []) + worker(Pleroma.Stats, []), + worker(Pleroma.Web.Push, []) ] ++ streamer_child() ++ chat_child() ++ diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index a3aeb1221..a40b8f8c9 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -110,6 +110,7 @@ defmodule Pleroma.Notification do notification = %Notification{user_id: user.id, activity: activity} {:ok, notification} = Repo.insert(notification) Pleroma.Web.Streamer.stream("user", notification) + Pleroma.Web.Push.send(notification) notification end end diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex index 03a75dfbd..31c8dd5bd 100644 --- a/lib/pleroma/object.ex +++ b/lib/pleroma/object.ex @@ -1,6 +1,6 @@ defmodule Pleroma.Object do use Ecto.Schema - alias Pleroma.{Repo, Object, Activity} + alias Pleroma.{Repo, Object, User, Activity} import Ecto.{Query, Changeset} schema "objects" do @@ -31,6 +31,13 @@ defmodule Pleroma.Object do def normalize(ap_id) when is_binary(ap_id), do: Object.get_by_ap_id(ap_id) def normalize(_), do: nil + # Owned objects can only be mutated by their owner + def authorize_mutation(%Object{data: %{"actor" => actor}}, %User{ap_id: ap_id}), + do: actor == ap_id + + # Legacy objects can be mutated by anybody + def authorize_mutation(%Object{}, %User{}), do: true + if Mix.env() == :test do def get_cached_by_ap_id(ap_id) do get_by_ap_id(ap_id) diff --git a/lib/pleroma/plugs/oauth_plug.ex b/lib/pleroma/plugs/oauth_plug.ex index 75f9209c2..8b99a74d1 100644 --- a/lib/pleroma/plugs/oauth_plug.ex +++ b/lib/pleroma/plugs/oauth_plug.ex @@ -17,7 +17,9 @@ defmodule Pleroma.Plugs.OAuthPlug do def call(conn, _) do with {:ok, token} <- fetch_token(conn), {:ok, user} <- fetch_user(token) do - assign(conn, :user, user) + conn + |> assign(:token, token) + |> assign(:user, user) else _ -> conn end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index a1637ccad..bf81d8039 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -574,7 +574,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do def upload(file, opts \\ []) do with {:ok, data} <- Upload.store(file, opts) do - Repo.insert(%Object{data: data}) + obj_data = + if opts[:actor] do + Map.put(data, "actor", opts[:actor]) + else + data + end + + Repo.insert(%Object{data: obj_data}) end end diff --git a/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex b/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex new file mode 100644 index 000000000..c8c74ede6 --- /dev/null +++ b/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex @@ -0,0 +1,40 @@ +defmodule Pleroma.Web.ActivityPub.MRF.EnsureRePrepended do + alias Pleroma.Object + + @behaviour Pleroma.Web.ActivityPub.MRF + + @reply_prefix Regex.compile!("^re:[[:space:]]*", [:caseless]) + def filter_by_summary( + %{"summary" => parent_summary} = parent, + %{"summary" => child_summary} = child + ) + when not is_nil(child_summary) and byte_size(child_summary) > 0 and + not is_nil(parent_summary) and byte_size(parent_summary) > 0 do + if (child_summary == parent_summary and not Regex.match?(@reply_prefix, child_summary)) or + (Regex.match?(@reply_prefix, parent_summary) && + Regex.replace(@reply_prefix, parent_summary, "") == child_summary) do + Map.put(child, "summary", "re: " <> child_summary) + else + child + end + end + + def filter_by_summary(parent, child), do: child + + def filter(%{"type" => activity_type} = object) when activity_type == "Create" do + child = object["object"] + in_reply_to = Object.normalize(child["inReplyTo"]) + + child = + if(in_reply_to, + do: filter_by_summary(in_reply_to.data, child), + else: child + ) + + object = Map.put(object, "object", child) + + {:ok, object} + end + + def filter(object), do: {:ok, object} +end diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex index fc4f3fdc7..57de92dc0 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -2,13 +2,22 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do use Pleroma.Web, :controller alias Pleroma.{Repo, Object, Activity, User, Notification, Stats} alias Pleroma.Web - alias Pleroma.Web.MastodonAPI.{StatusView, AccountView, MastodonView, ListView, FilterView} + + alias Pleroma.Web.MastodonAPI.{ + StatusView, + AccountView, + MastodonView, + ListView, + FilterView, + PushSubscriptionView + } + alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.CommonAPI alias Pleroma.Web.OAuth.{Authorization, Token, App} alias Pleroma.Web.MediaProxy - alias Comeonin.Pbkdf2 + import Ecto.Query require Logger @@ -433,33 +442,31 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do |> json([]) end - def update_media(%{assigns: %{user: _}} = conn, data) do + def update_media(%{assigns: %{user: user}} = conn, data) do with %Object{} = object <- Repo.get(Object, data["id"]), + true <- Object.authorize_mutation(object, user), true <- is_binary(data["description"]), description <- data["description"] do new_data = %{object.data | "name" => description} - change = Object.change(object, %{data: new_data}) - {:ok, _} = Repo.update(change) + {:ok, _} = + object + |> Object.change(%{data: new_data}) + |> Repo.update() - data = - new_data - |> Map.put("id", object.id) - - render(conn, StatusView, "attachment.json", %{attachment: data}) + attachment_data = Map.put(new_data, "id", object.id) + render(conn, StatusView, "attachment.json", %{attachment: attachment_data}) end end - def upload(%{assigns: %{user: _}} = conn, %{"file" => file} = data) do - with {:ok, object} <- ActivityPub.upload(file, description: Map.get(data, "description")) do - change = Object.change(object, %{data: object.data}) - {:ok, object} = Repo.update(change) - - objdata = - object.data - |> Map.put("id", object.id) - - render(conn, StatusView, "attachment.json", %{attachment: objdata}) + def upload(%{assigns: %{user: user}} = conn, %{"file" => file} = data) do + with {:ok, object} <- + ActivityPub.upload(file, + actor: User.ap_id(user), + description: Map.get(data, "description") + ) do + attachment_data = Map.put(object.data, "id", object.id) + render(conn, StatusView, "attachment.json", %{attachment: attachment_data}) end end @@ -1162,6 +1169,33 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do json(conn, %{}) end + def create_push_subscription(%{assigns: %{user: user, token: token}} = conn, params) do + Pleroma.Web.Push.Subscription.delete_if_exists(user, token) + {:ok, subscription} = Pleroma.Web.Push.Subscription.create(user, token, params) + view = PushSubscriptionView.render("push_subscription.json", subscription: subscription) + json(conn, view) + end + + def get_push_subscription(%{assigns: %{user: user, token: token}} = conn, _params) do + subscription = Pleroma.Web.Push.Subscription.get(user, token) + view = PushSubscriptionView.render("push_subscription.json", subscription: subscription) + json(conn, view) + end + + def update_push_subscription( + %{assigns: %{user: user, token: token}} = conn, + params + ) do + {:ok, subscription} = Pleroma.Web.Push.Subscription.update(user, token, params) + view = PushSubscriptionView.render("push_subscription.json", subscription: subscription) + json(conn, view) + end + + def delete_push_subscription(%{assigns: %{user: user, token: token}} = conn, _params) do + {:ok, _response} = Pleroma.Web.Push.Subscription.delete(user, token) + json(conn, %{}) + end + def errors(conn, _) do conn |> put_status(500) diff --git a/lib/pleroma/web/mastodon_api/views/push_subscription_view.ex b/lib/pleroma/web/mastodon_api/views/push_subscription_view.ex new file mode 100644 index 000000000..68bb45494 --- /dev/null +++ b/lib/pleroma/web/mastodon_api/views/push_subscription_view.ex @@ -0,0 +1,12 @@ +defmodule Pleroma.Web.MastodonAPI.PushSubscriptionView do + use Pleroma.Web, :view + alias Pleroma.Web.MastodonAPI.PushSubscriptionView + + def render("push_subscription.json", %{subscription: subscription}) do + %{ + id: to_string(subscription.id), + endpoint: subscription.endpoint, + alerts: Map.get(subscription.data, "alerts") + } + end +end diff --git a/lib/pleroma/web/push/push.ex b/lib/pleroma/web/push/push.ex new file mode 100644 index 000000000..5a873ec19 --- /dev/null +++ b/lib/pleroma/web/push/push.ex @@ -0,0 +1,116 @@ +defmodule Pleroma.Web.Push do + use GenServer + + alias Pleroma.{Repo, User} + alias Pleroma.Web.Push.Subscription + + require Logger + import Ecto.Query + + @types ["Create", "Follow", "Announce", "Like"] + + @gcm_api_key nil + + def start_link() do + GenServer.start_link(__MODULE__, :ok, name: __MODULE__) + end + + def init(:ok) do + case Application.get_env(:web_push_encryption, :vapid_details) do + nil -> + Logger.warn( + "VAPID key pair is not found. Please, add VAPID configuration to config. Run `mix web_push.gen.keypair` mix task to create a key pair" + ) + + :ignore + + _ -> + {:ok, %{}} + end + end + + def send(notification) do + if Application.get_env(:web_push_encryption, :vapid_details) do + GenServer.cast(Pleroma.Web.Push, {:send, notification}) + end + end + + def handle_cast( + {:send, %{activity: %{data: %{"type" => type}}, user_id: user_id} = notification}, + state + ) + when type in @types do + actor = User.get_cached_by_ap_id(notification.activity.data["actor"]) + body = notification |> format(actor) |> Jason.encode!() + + Subscription + |> where(user_id: ^user_id) + |> Repo.all() + |> Enum.each(fn record -> + subscription = %{ + keys: %{ + p256dh: record.key_p256dh, + auth: record.key_auth + }, + endpoint: record.endpoint + } + + case WebPushEncryption.send_web_push(body, subscription, @gcm_api_key) do + {:ok, %{status_code: code}} when 400 <= code and code < 500 -> + Logger.debug("Removing subscription record") + Repo.delete!(record) + :ok + + {:ok, %{status_code: code}} when 200 <= code and code < 300 -> + :ok + + {:ok, %{status_code: code}} -> + Logger.error("Web Push Nonification failed with code: #{code}") + :error + + _ -> + Logger.error("Web Push Nonification failed with unknown error") + :error + end + end) + + {:noreply, state} + end + + def handle_cast({:send, _}, state) do + Logger.warn("Unknown notification type") + {:noreply, state} + end + + def format(%{activity: %{data: %{"type" => "Create"}}}, actor) do + %{ + title: "New Mention", + body: "@#{actor.nickname} has mentiond you", + icon: User.avatar_url(actor) + } + end + + def format(%{activity: %{data: %{"type" => "Follow"}}}, actor) do + %{ + title: "New Follower", + body: "@#{actor.nickname} has followed you", + icon: User.avatar_url(actor) + } + end + + def format(%{activity: %{data: %{"type" => "Announce"}}}, actor) do + %{ + title: "New Announce", + body: "@#{actor.nickname} has announced your post", + icon: User.avatar_url(actor) + } + end + + def format(%{activity: %{data: %{"type" => "Like"}}}, actor) do + %{ + title: "New Like", + body: "@#{actor.nickname} has liked your post", + icon: User.avatar_url(actor) + } + end +end diff --git a/lib/pleroma/web/push/subscription.ex b/lib/pleroma/web/push/subscription.ex new file mode 100644 index 000000000..cfab7a98e --- /dev/null +++ b/lib/pleroma/web/push/subscription.ex @@ -0,0 +1,66 @@ +defmodule Pleroma.Web.Push.Subscription do + use Ecto.Schema + import Ecto.Changeset + alias Pleroma.{Repo, User} + alias Pleroma.Web.OAuth.Token + alias Pleroma.Web.Push.Subscription + + schema "push_subscriptions" do + belongs_to(:user, User) + belongs_to(:token, Token) + field(:endpoint, :string) + field(:key_p256dh, :string) + field(:key_auth, :string) + field(:data, :map, default: %{}) + + timestamps() + end + + @supported_alert_types ~w[follow favourite mention reblog] + + defp alerts(%{"data" => %{"alerts" => alerts}}) do + alerts = Map.take(alerts, @supported_alert_types) + %{"alerts" => alerts} + end + + def create( + %User{} = user, + %Token{} = token, + %{ + "subscription" => %{ + "endpoint" => endpoint, + "keys" => %{"auth" => key_auth, "p256dh" => key_p256dh} + } + } = params + ) do + Repo.insert(%Subscription{ + user_id: user.id, + token_id: token.id, + endpoint: endpoint, + key_auth: key_auth, + key_p256dh: key_p256dh, + data: alerts(params) + }) + end + + def get(%User{id: user_id}, %Token{id: token_id}) do + Repo.get_by(Subscription, user_id: user_id, token_id: token_id) + end + + def update(user, token, params) do + get(user, token) + |> change(data: alerts(params)) + |> Repo.update() + end + + def delete(user, token) do + Repo.delete(get(user, token)) + end + + def delete_if_exists(user, token) do + case get(user, token) do + nil -> {:ok, nil} + sub -> Repo.delete(sub) + end + end +end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index d6a9d5779..75d965c6d 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -198,6 +198,11 @@ defmodule Pleroma.Web.Router do put("/filters/:id", MastodonAPIController, :update_filter) delete("/filters/:id", MastodonAPIController, :delete_filter) + post("/push/subscription", MastodonAPIController, :create_push_subscription) + get("/push/subscription", MastodonAPIController, :get_push_subscription) + put("/push/subscription", MastodonAPIController, :update_push_subscription) + delete("/push/subscription", MastodonAPIController, :delete_push_subscription) + get("/suggestions", MastodonAPIController, :suggestions) get("/endorsements", MastodonAPIController, :empty_array) @@ -324,6 +329,7 @@ defmodule Pleroma.Web.Router do post("/statusnet/media/upload", TwitterAPI.Controller, :upload) post("/media/upload", TwitterAPI.Controller, :upload_json) + post("/media/metadata/create", TwitterAPI.Controller, :update_media) post("/favorites/create/:id", TwitterAPI.Controller, :favorite) post("/favorites/create", TwitterAPI.Controller, :favorite) diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex index b0ed8387e..092779010 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex @@ -157,13 +157,17 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do |> send_resp(200, response) _ -> + vapid_public_key = + Keyword.get(Application.get_env(:web_push_encryption, :vapid_details), :public_key) + data = %{ name: Keyword.get(instance, :name), description: Keyword.get(instance, :description), server: Web.base_url(), textlimit: to_string(Keyword.get(instance, :limit)), closed: if(Keyword.get(instance, :registrations_open), do: "0", else: "1"), - private: if(Keyword.get(instance, :public, true), do: "0", else: "1") + private: if(Keyword.get(instance, :public, true), do: "0", else: "1"), + vapidPublicKey: vapid_public_key } pleroma_fe = %{ diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex index c19a4f084..9c485d965 100644 --- a/lib/pleroma/web/twitter_api/twitter_api.ex +++ b/lib/pleroma/web/twitter_api/twitter_api.ex @@ -93,8 +93,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do end end - def upload(%Plug.Upload{} = file, format \\ "xml") do - {:ok, object} = ActivityPub.upload(file) + def upload(%Plug.Upload{} = file, %User{} = user, format \\ "xml") do + {:ok, object} = ActivityPub.upload(file, actor: User.ap_id(user)) url = List.first(object.data["url"]) href = url["href"] diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/twitter_api_controller.ex index 8fd6ea078..0ccf937b0 100644 --- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex +++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex @@ -4,7 +4,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do alias Pleroma.Web.TwitterAPI.{TwitterAPI, UserView, ActivityView, NotificationView} alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils - alias Pleroma.{Repo, Activity, User, Notification} + alias Pleroma.{Repo, Activity, Object, User, Notification} alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Utils alias Ecto.Changeset @@ -226,16 +226,51 @@ defmodule Pleroma.Web.TwitterAPI.Controller do end end - def upload(conn, %{"media" => media}) do - response = TwitterAPI.upload(media) + @doc """ + Updates metadata of uploaded media object. + Derived from [Twitter API endpoint](https://developer.twitter.com/en/docs/media/upload-media/api-reference/post-media-metadata-create). + """ + def update_media(%{assigns: %{user: user}} = conn, %{"media_id" => id} = data) do + object = Repo.get(Object, id) + description = get_in(data, ["alt_text", "text"]) || data["name"] || data["description"] + + {conn, status, response_body} = + cond do + !object -> + {halt(conn), :not_found, ""} + + !Object.authorize_mutation(object, user) -> + {halt(conn), :forbidden, "You can only update your own uploads."} + + !is_binary(description) -> + {conn, :not_modified, ""} + + true -> + new_data = Map.put(object.data, "name", description) + + {:ok, _} = + object + |> Object.change(%{data: new_data}) + |> Repo.update() + + {conn, :no_content, ""} + end + + conn + |> put_status(status) + |> json(response_body) + end + + def upload(%{assigns: %{user: user}} = conn, %{"media" => media}) do + response = TwitterAPI.upload(media, user) conn |> put_resp_content_type("application/atom+xml") |> send_resp(200, response) end - def upload_json(conn, %{"media" => media}) do - response = TwitterAPI.upload(media, "json") + def upload_json(%{assigns: %{user: user}} = conn, %{"media" => media}) do + response = TwitterAPI.upload(media, user, "json") conn |> json_reply(200, response) diff --git a/mix.exs b/mix.exs index 1a28b6710..bd9bce766 100644 --- a/mix.exs +++ b/mix.exs @@ -68,7 +68,8 @@ defmodule Pleroma.Mixfile do {:crypt, git: "https://github.com/msantos/crypt", ref: "1f2b58927ab57e72910191a7ebaeff984382a1d3"}, {:cors_plug, "~> 1.5"}, - {:ex_doc, "> 0.18.3 and < 0.20.0", only: :dev, runtime: false} + {:ex_doc, "> 0.18.3 and < 0.20.0", only: :dev, runtime: false}, + {:web_push_encryption, "~> 0.2.1"} ] end diff --git a/mix.lock b/mix.lock index 4c70061d3..ff8e9fdca 100644 --- a/mix.lock +++ b/mix.lock @@ -1,4 +1,5 @@ %{ + "base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"}, "cachex": {:hex, :cachex, "3.0.2", "1351caa4e26e29f7d7ec1d29b53d6013f0447630bbf382b4fb5d5bad0209f203", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm"}, "calendar": {:hex, :calendar, "0.17.4", "22c5e8d98a4db9494396e5727108dffb820ee0d18fed4b0aa8ab76e4f5bc32f1", [:mix], [{:tzdata, "~> 0.5.8 or ~> 0.1.201603", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"}, @@ -25,6 +26,7 @@ "httpoison": {:hex, :httpoison, "1.2.0", "2702ed3da5fd7a8130fc34b11965c8cfa21ade2f232c00b42d96d4967c39a3a3", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, "idna": {:hex, :idna, "5.1.2", "e21cb58a09f0228a9e0b95eaa1217f1bcfc31a1aaa6e1fdf2f53a33f7dbd9494", [:rebar3], [{:unicode_util_compat, "0.3.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"}, "jason": {:hex, :jason, "1.0.0", "0f7cfa9bdb23fed721ec05419bcee2b2c21a77e926bce0deda029b5adc716fe2", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"}, + "jose": {:hex, :jose, "1.8.4", "7946d1e5c03a76ac9ef42a6e6a20001d35987afd68c2107bcd8f01a84e75aa73", [:mix, :rebar3], [{:base64url, "~> 0.0.1", [hex: :base64url, repo: "hexpm", optional: false]}], "hexpm"}, "makeup": {:hex, :makeup, "0.5.5", "9e08dfc45280c5684d771ad58159f718a7b5788596099bdfb0284597d368a882", [:mix], [{:nimble_parsec, "~> 0.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"}, "makeup_elixir": {:hex, :makeup_elixir, "0.10.0", "0f09c2ddf352887a956d84f8f7e702111122ca32fbbc84c2f0569b8b65cbf7fa", [:mix], [{:makeup, "~> 0.5.5", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"}, "meck": {:hex, :meck, "0.8.9", "64c5c0bd8bcca3a180b44196265c8ed7594e16bcc845d0698ec6b4e577f48188", [:rebar3], [], "hexpm"}, @@ -52,4 +54,5 @@ "tzdata": {:hex, :tzdata, "0.5.17", "50793e3d85af49736701da1a040c415c97dc1caf6464112fd9bd18f425d3053b", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.3.1", "a1f612a7b512638634a603c8f401892afbf99b8ce93a45041f8aaca99cadb85e", [:rebar3], [], "hexpm"}, "unsafe": {:hex, :unsafe, "1.0.0", "7c21742cd05380c7875546b023481d3a26f52df8e5dfedcb9f958f322baae305", [:mix], [], "hexpm"}, + "web_push_encryption": {:hex, :web_push_encryption, "0.2.1", "d42cecf73420d9dc0053ba3299cc8c8d6ff2be2487d67ca2a57265868e4d9a98", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:poison, "~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, } diff --git a/priv/repo/migrations/20180918182427_create_push_subscriptions.exs b/priv/repo/migrations/20180918182427_create_push_subscriptions.exs new file mode 100644 index 000000000..0cc7afa54 --- /dev/null +++ b/priv/repo/migrations/20180918182427_create_push_subscriptions.exs @@ -0,0 +1,18 @@ +defmodule Pleroma.Repo.Migrations.CreatePushSubscriptions do + use Ecto.Migration + + def change do + create table("push_subscriptions") do + add :user_id, references("users", on_delete: :delete_all) + add :token_id, references("oauth_tokens", on_delete: :delete_all) + add :endpoint, :string + add :key_p256dh, :string + add :key_auth, :string + add :data, :map + + timestamps() + end + + create index("push_subscriptions", [:user_id, :token_id], unique: true) + end +end diff --git a/test/support/data_case.ex b/test/support/data_case.ex index 8eff0fd94..9dde6b5e5 100644 --- a/test/support/data_case.ex +++ b/test/support/data_case.ex @@ -36,6 +36,23 @@ defmodule Pleroma.DataCase do :ok end + def ensure_local_uploader(_context) do + uploader = Pleroma.Config.get([Pleroma.Upload, :uploader]) + filters = Pleroma.Config.get([Pleroma.Upload, :filters]) + + unless uploader == Pleroma.Uploaders.Local || filters != [] do + Pleroma.Config.put([Pleroma.Upload, :uploader], Pleroma.Uploaders.Local) + Pleroma.Config.put([Pleroma.Upload, :filters], []) + + on_exit(fn -> + Pleroma.Config.put([Pleroma.Upload, :uploader], uploader) + Pleroma.Config.put([Pleroma.Upload, :filters], filters) + end) + end + + :ok + end + @doc """ A helper that transform changeset errors to a map of messages. diff --git a/test/upload_test.exs b/test/upload_test.exs index b2ce755d2..f2cad4cf0 100644 --- a/test/upload_test.exs +++ b/test/upload_test.exs @@ -3,22 +3,7 @@ defmodule Pleroma.UploadTest do use Pleroma.DataCase describe "Storing a file with the Local uploader" do - setup do - uploader = Pleroma.Config.get([Pleroma.Upload, :uploader]) - filters = Pleroma.Config.get([Pleroma.Upload, :filters]) - - unless uploader == Pleroma.Uploaders.Local || filters != [] do - Pleroma.Config.put([Pleroma.Upload, :uploader], Pleroma.Uploaders.Local) - Pleroma.Config.put([Pleroma.Upload, :filters], []) - - on_exit(fn -> - Pleroma.Config.put([Pleroma.Upload, :uploader], uploader) - Pleroma.Config.put([Pleroma.Upload, :filters], filters) - end) - end - - :ok - end + setup [:ensure_local_uploader] test "returns a media url" do File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg") diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs index 0ff611648..092f0c9fc 100644 --- a/test/web/mastodon_api/mastodon_api_controller_test.exs +++ b/test/web/mastodon_api/mastodon_api_controller_test.exs @@ -2,7 +2,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do use Pleroma.Web.ConnCase alias Pleroma.Web.TwitterAPI.TwitterAPI - alias Pleroma.{Repo, User, Activity, Notification} + alias Pleroma.{Repo, User, Object, Activity, Notification} alias Pleroma.Web.{OStatus, CommonAPI} alias Pleroma.Web.ActivityPub.ActivityPub @@ -810,7 +810,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do } media = - TwitterAPI.upload(file, "json") + TwitterAPI.upload(file, user, "json") |> Poison.decode!() {:ok, image_post} = @@ -965,6 +965,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do assert media["type"] == "image" assert media["description"] == desc + assert media["id"] + + object = Repo.get(Object, media["id"]) + assert object.data["actor"] == User.ap_id(user) end test "hashtag timeline", %{conn: conn} do diff --git a/test/web/twitter_api/twitter_api_controller_test.exs b/test/web/twitter_api/twitter_api_controller_test.exs index a8a9da781..4119d1dd8 100644 --- a/test/web/twitter_api/twitter_api_controller_test.exs +++ b/test/web/twitter_api/twitter_api_controller_test.exs @@ -1376,4 +1376,82 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do assert [user.id, user_two.id, user_three.id] == Enum.map(resp, fn %{"id" => id} -> id end) end end + + describe "POST /api/media/upload" do + setup context do + Pleroma.DataCase.ensure_local_uploader(context) + end + + test "it performs the upload and sets `data[actor]` with AP id of uploader user", %{ + conn: conn + } do + user = insert(:user) + + upload_filename = "test/fixtures/image_tmp.jpg" + File.cp!("test/fixtures/image.jpg", upload_filename) + + file = %Plug.Upload{ + content_type: "image/jpg", + path: Path.absname(upload_filename), + filename: "image.jpg" + } + + response = + conn + |> assign(:user, user) + |> put_req_header("content-type", "application/octet-stream") + |> post("/api/media/upload", %{ + "media" => file + }) + |> json_response(:ok) + + assert response["media_id"] + object = Repo.get(Object, response["media_id"]) + assert object + assert object.data["actor"] == User.ap_id(user) + end + end + + describe "POST /api/media/metadata/create" do + setup do + object = insert(:note) + user = User.get_by_ap_id(object.data["actor"]) + %{object: object, user: user} + end + + test "it returns :forbidden status on attempt to modify someone else's upload", %{ + conn: conn, + object: object + } do + initial_description = object.data["name"] + another_user = insert(:user) + + conn + |> assign(:user, another_user) + |> post("/api/media/metadata/create", %{"media_id" => object.id}) + |> json_response(:forbidden) + + object = Repo.get(Object, object.id) + assert object.data["name"] == initial_description + end + + test "it updates `data[name]` of referenced Object with provided value", %{ + conn: conn, + object: object, + user: user + } do + description = "Informative description of the image. Initial value: #{object.data["name"]}}" + + conn + |> assign(:user, user) + |> post("/api/media/metadata/create", %{ + "media_id" => object.id, + "alt_text" => %{"text" => description} + }) + |> json_response(:no_content) + + object = Repo.get(Object, object.id) + assert object.data["name"] == description + end + end end diff --git a/test/web/twitter_api/twitter_api_test.exs b/test/web/twitter_api/twitter_api_test.exs index 76de68783..05f832de0 100644 --- a/test/web/twitter_api/twitter_api_test.exs +++ b/test/web/twitter_api/twitter_api_test.exs @@ -182,13 +182,15 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do end test "upload a file" do + user = insert(:user) + file = %Plug.Upload{ content_type: "image/jpg", path: Path.absname("test/fixtures/image.jpg"), filename: "an_image.jpg" } - response = TwitterAPI.upload(file) + response = TwitterAPI.upload(file, user) assert is_binary(response) end