diff options
Diffstat (limited to 'lib')
23 files changed, 677 insertions, 83 deletions
diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex index f226c4c5f..9a5e6fc78 100644 --- a/lib/pleroma/activity.ex +++ b/lib/pleroma/activity.ex @@ -1,11 +1,12 @@ defmodule Pleroma.Activity do use Ecto.Schema - alias Pleroma.{Repo, Activity} + alias Pleroma.{Repo, Activity, Notification} import Ecto.Query schema "activities" do field :data, :map field :local, :boolean, default: true + has_many :notifications, Notification timestamps() end diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex new file mode 100644 index 000000000..4a9e835bf --- /dev/null +++ b/lib/pleroma/notification.ex @@ -0,0 +1,38 @@ +defmodule Pleroma.Notification do + use Ecto.Schema + alias Pleroma.{User, Activity, Notification, Repo} + import Ecto.Query + + schema "notifications" do + field :seen, :boolean, default: false + belongs_to :user, Pleroma.User + belongs_to :activity, Pleroma.Activity + + timestamps() + end + + def for_user(user, opts \\ %{}) do + query = from n in Notification, + where: n.user_id == ^user.id, + order_by: [desc: n.id], + preload: [:activity], + limit: 20 + Repo.all(query) + end + + def create_notifications(%Activity{id: id, data: %{"to" => to, "type" => type}} = activity) when type in ["Create", "Like", "Announce", "Follow"] do + users = User.get_notified_from_activity(activity) + + notifications = Enum.map(users, fn (user) -> create_notification(activity, user) end) + {:ok, notifications} + end + def create_notifications(_), do: {:ok, []} + + # TODO move to sql, too. + def create_notification(%Activity{} = activity, %User{} = user) do + notification = %Notification{user_id: user.id, activity_id: activity.id} + {:ok, notification} = Repo.insert(notification) + notification + end +end + diff --git a/lib/pleroma/plugs/oauth_plug.ex b/lib/pleroma/plugs/oauth_plug.ex new file mode 100644 index 000000000..fc2a907a2 --- /dev/null +++ b/lib/pleroma/plugs/oauth_plug.ex @@ -0,0 +1,22 @@ +defmodule Pleroma.Plugs.OAuthPlug do + import Plug.Conn + alias Pleroma.User + alias Pleroma.Repo + alias Pleroma.Web.OAuth.Token + + def init(options) do + options + end + + def call(%{assigns: %{user: %User{}}} = conn, _), do: conn + def call(conn, opts) do + with ["Bearer " <> header] <- get_req_header(conn, "authorization"), + %Token{user_id: user_id} <- Repo.get_by(Token, token: header), + %User{} = user <- Repo.get(User, user_id) do + conn + |> assign(:user, user) + else + _ -> conn + end + end +end diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 4f5fcab5b..39d8cca76 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -2,7 +2,7 @@ defmodule Pleroma.User do use Ecto.Schema import Ecto.{Changeset, Query} - alias Pleroma.{Repo, User, Object, Web} + alias Pleroma.{Repo, User, Object, Web, Activity, Notification} alias Comeonin.Pbkdf2 alias Pleroma.Web.{OStatus, Websub} alias Pleroma.Web.ActivityPub.ActivityPub @@ -22,6 +22,7 @@ defmodule Pleroma.User do field :local, :boolean, default: true field :info, :map, default: %{} field :follower_address, :string + has_many :notifications, Notification timestamps() end @@ -239,4 +240,12 @@ defmodule Pleroma.User do Repo.update(cs) end + + def get_notified_from_activity(%Activity{data: %{"to" => to}} = activity) do + query = from u in User, + where: u.ap_id in ^to, + where: u.local == true + + Repo.all(query) + end end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index db1302738..e3dce9cba 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -1,5 +1,5 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do - alias Pleroma.{Activity, Repo, Object, Upload, User, Web} + alias Pleroma.{Activity, Repo, Object, Upload, User, Web, Notification} alias Ecto.{Changeset, UUID} import Ecto.Query import Pleroma.Web.ActivityPub.Utils @@ -9,7 +9,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do with nil <- Activity.get_by_ap_id(map["id"]), map <- lazy_put_activity_defaults(map), :ok <- insert_full_object(map) do - Repo.insert(%Activity{data: map, local: local}) + {:ok, activity} = Repo.insert(%Activity{data: map, local: local}) + Notification.create_notifications(activity) + {:ok, activity} else %Activity{} = activity -> {:ok, activity} error -> {:error, error} @@ -133,6 +135,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end defp restrict_actor(query, _), do: query + defp restrict_type(query, %{"type" => type}) do + from activity in query, + where: fragment("?->>'type' = ?", activity.data, ^type) + end + defp restrict_type(query, _), do: query + def fetch_activities(recipients, opts \\ %{}) do base_query = from activity in Activity, limit: 20, @@ -144,6 +152,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do |> restrict_local(opts) |> restrict_max(opts) |> restrict_actor(opts) + |> restrict_type(opts) |> Repo.all |> Enum.reverse end diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex new file mode 100644 index 000000000..b08138534 --- /dev/null +++ b/lib/pleroma/web/common_api/common_api.ex @@ -0,0 +1,56 @@ +defmodule Pleroma.Web.CommonAPI do + alias Pleroma.{Repo, Activity, Object} + alias Pleroma.Web.ActivityPub.ActivityPub + + def delete(activity_id, user) do + with %Activity{data: %{"object" => %{"id" => object_id}}} <- Repo.get(Activity, activity_id), + %Object{} = object <- Object.get_by_ap_id(object_id), + true <- user.ap_id == object.data["actor"], + {:ok, delete} <- ActivityPub.delete(object) do + {:ok, delete} + end + end + + def repeat(id_or_ap_id, user) do + with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id), + false <- activity.data["actor"] == user.ap_id, + object <- Object.get_by_ap_id(activity.data["object"]["id"]) do + ActivityPub.announce(user, object) + else + _ -> + {:error, "Could not repeat"} + end + end + + def favorite(id_or_ap_id, user) do + with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id), + false <- activity.data["actor"] == user.ap_id, + object <- Object.get_by_ap_id(activity.data["object"]["id"]) do + ActivityPub.like(user, object) + else + _ -> + {:error, "Could not favorite"} + end + end + + def unfavorite(id_or_ap_id, user) do + with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id), + false <- activity.data["actor"] == user.ap_id, + object <- Object.get_by_ap_id(activity.data["object"]["id"]) do + ActivityPub.unlike(user, object) + else + _ -> + {:error, "Could not unfavorite"} + end + end + + # This is a hack for twidere. + def get_by_id_or_ap_id(id) do + activity = Repo.get(Activity, id) || Activity.get_create_activity_by_object_ap_id(id) + if activity.data["type"] == "Create" do + activity + else + Activity.get_create_activity_by_object_ap_id(activity.data["object"]) + end + end +end diff --git a/lib/pleroma/web/mastodon_api/mastodon_api.ex b/lib/pleroma/web/mastodon_api/mastodon_api.ex new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/lib/pleroma/web/mastodon_api/mastodon_api.ex diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex new file mode 100644 index 000000000..9e4d13b3a --- /dev/null +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -0,0 +1,162 @@ +defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do + use Pleroma.Web, :controller + alias Pleroma.{Repo, Activity, User, Notification} + alias Pleroma.Web.OAuth.App + alias Pleroma.Web + alias Pleroma.Web.MastodonAPI.{StatusView, AccountView} + alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.TwitterAPI.TwitterAPI + alias Pleroma.Web.CommonAPI + import Logger + + def create_app(conn, params) do + with cs <- App.register_changeset(%App{}, params) |> IO.inspect, + {:ok, app} <- Repo.insert(cs) |> IO.inspect do + res = %{ + id: app.id, + client_id: app.client_id, + client_secret: app.client_secret + } + + json(conn, res) + end + end + + def verify_credentials(%{assigns: %{user: user}} = conn, params) do + account = AccountView.render("account.json", %{user: user}) + json(conn, account) + end + + def masto_instance(conn, _params) do + response = %{ + uri: Web.base_url, + title: Web.base_url, + description: "A Pleroma instance, an alternative fediverse server", + version: "Pleroma Dev" + } + + json(conn, response) + end + + def home_timeline(%{assigns: %{user: user}} = conn, params) do + activities = ActivityPub.fetch_activities([user.ap_id | user.following], Map.put(params, "type", "Create")) + |> Enum.reverse + render conn, StatusView, "index.json", %{activities: activities, for: user, as: :activity} + end + + def public_timeline(%{assigns: %{user: user}} = conn, params) do + params = params + |> Map.put("type", "Create") + |> Map.put("local_only", !!params["local"]) + + activities = ActivityPub.fetch_public_activities(params) + |> Enum.reverse + + render conn, StatusView, "index.json", %{activities: activities, for: user, as: :activity} + end + + def user_statuses(%{assigns: %{user: user}} = conn, params) do + with %User{ap_id: ap_id} <- Repo.get(User, params["id"]) do + params = params + |> Map.put("type", "Create") + |> Map.put("actor_id", ap_id) + + activities = ActivityPub.fetch_activities([], params) + |> Enum.reverse + + render conn, StatusView, "index.json", %{activities: activities, for: user, as: :activity} + end + end + + def get_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do + with %Activity{} = activity <- Repo.get(Activity, id) do + render conn, StatusView, "status.json", %{activity: activity, for: user} + end + end + + def get_context(%{assigns: %{user: user}} = conn, %{"id" => id}) do + with %Activity{} = activity <- Repo.get(Activity, id), + activities <- ActivityPub.fetch_activities_for_context(activity.data["object"]["context"]), + activities <- activities |> Enum.filter(fn (%{id: aid}) -> to_string(aid) != to_string(id) end), + grouped_activities <- Enum.group_by(activities, fn (%{id: id}) -> id < activity.id end) do + result = %{ + ancestors: StatusView.render("index.json", for: user, activities: grouped_activities[true] || [], as: :activity) |> Enum.reverse, + descendants: StatusView.render("index.json", for: user, activities: grouped_activities[false] || [], as: :activity) |> Enum.reverse, + } + + json(conn, result) + end + end + + def post_status(%{assigns: %{user: user}} = conn, %{"status" => status} = params) do + l = status |> String.trim |> String.length + + params = params + |> Map.put("in_reply_to_status_id", params["in_reply_to_id"]) + + if l > 0 && l < 5000 do + {:ok, activity} = TwitterAPI.create_status(user, params) + render conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity} + end + end + + def delete_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do + with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do + json(conn, %{}) + else + _e -> + conn + |> put_status(403) + |> json(%{error: "Can't delete this post"}) + end + end + + def reblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do + with {:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.repeat(ap_id_or_id, user), + %Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do + render conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity} + end + end + + def fav_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do + with {:ok, _fav, %{data: %{"id" => id}}} = CommonAPI.favorite(ap_id_or_id, user), + %Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do + render conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity} + end + end + + def unfav_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do + with {:ok, %{data: %{"id" => id}}} = CommonAPI.unfavorite(ap_id_or_id, user), + %Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do + render conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity} + end + end + + def notifications(%{assigns: %{user: user}} = conn, params) do + notifications = Notification.for_user(user, params) + result = Enum.map(notifications, fn (%{id: id, activity: activity, inserted_at: created_at}) -> + actor = User.get_cached_by_ap_id(activity.data["actor"]) + case activity.data["type"] do + "Create" -> + %{id: id, type: "mention", created_at: created_at, account: AccountView.render("account.json", %{user: actor}), status: StatusView.render("status.json", %{activity: activity})} + "Like" -> + liked_activity = Activity.get_create_activity_by_object_ap_id(activity.data["object"]) + %{id: id, type: "favourite", created_at: created_at, account: AccountView.render("account.json", %{user: actor}), status: StatusView.render("status.json", %{activity: liked_activity})} + "Announce" -> + announced_activity = Activity.get_create_activity_by_object_ap_id(activity.data["object"]) + %{id: id, type: "reblog", created_at: created_at, account: AccountView.render("account.json", %{user: actor}), status: StatusView.render("status.json", %{activity: announced_activity})} + "Follow" -> + %{id: id, type: "follow", created_at: created_at, account: AccountView.render("account.json", %{user: actor})} + _ -> nil + end + end) + |> Enum.filter(&(&1)) + + json(conn, result) + end + + def empty_array(conn, _) do + Logger.debug("Unimplemented, returning an empty array") + json(conn, []) + end +end diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex new file mode 100644 index 000000000..35a130b1e --- /dev/null +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -0,0 +1,41 @@ +defmodule Pleroma.Web.MastodonAPI.AccountView do + use Pleroma.Web, :view + alias Pleroma.User + + defp image_url(%{"url" => [ %{ "href" => href } | t ]}), do: href + defp image_url(_), do: nil + + def render("account.json", %{user: user}) do + image = User.avatar_url(user) + user_info = User.user_info(user) + + header = image_url(user.info["banner"]) || "https://placehold.it/700x335" + + %{ + id: user.id, + username: user.nickname, + acct: user.nickname, + display_name: user.name, + locked: false, + created_at: user.inserted_at, + followers_count: user_info.follower_count, + following_count: user_info.following_count, + statuses_count: user_info.note_count, + note: user.bio, + url: user.ap_id, + avatar: image, + avatar_static: image, + header: header, + header_static: header + } + end + + def render("mention.json", %{user: user}) do + %{ + id: user.id, + acct: user.nickname, + username: user.nickname, + url: user.ap_id + } + end +end diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex new file mode 100644 index 000000000..686ffd29d --- /dev/null +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -0,0 +1,72 @@ +defmodule Pleroma.Web.MastodonAPI.StatusView do + use Pleroma.Web, :view + alias Pleroma.Web.MastodonAPI.{AccountView, StatusView} + alias Pleroma.User + + def render("index.json", opts) do + render_many(opts.activities, StatusView, "status.json", opts) + end + + def render("status.json", %{activity: %{data: %{"object" => object}} = activity} = opts) do + user = User.get_cached_by_ap_id(activity.data["actor"]) + + like_count = object["like_count"] || 0 + announcement_count = object["announcement_count"] || 0 + + tags = object["tag"] || [] + sensitive = Enum.member?(tags, "nsfw") + + mentions = activity.data["to"] + |> Enum.map(fn (ap_id) -> User.get_cached_by_ap_id(ap_id) end) + |> Enum.filter(&(&1)) + |> Enum.map(fn (user) -> AccountView.render("mention.json", %{user: user}) end) + + repeated = opts[:for] && opts[:for].ap_id in (object["announcements"] || []) + favorited = opts[:for] && opts[:for].ap_id in (object["likes"] || []) + + attachments = render_many(object["attachment"] || [], StatusView, "attachment.json", as: :attachment) + + %{ + id: activity.id, + uri: object["id"], + url: object["external_url"], + account: AccountView.render("account.json", %{user: user}), + in_reply_to_id: object["inReplyToStatusId"], + in_reply_to_account_id: nil, + reblog: nil, + content: HtmlSanitizeEx.basic_html(object["content"]), + created_at: object["published"], + reblogs_count: announcement_count, + favourites_count: like_count, + reblogged: !!repeated, + favourited: !!favorited, + muted: false, + sensitive: sensitive, + spoiler_text: "", + visibility: "public", + media_attachments: attachments, + mentions: mentions, + tags: [], # fix, + application: nil, + language: nil + } + end + + def render("attachment.json", %{attachment: attachment}) do + [%{"mediaType" => media_type, "href" => href} | _] = attachment["url"] + + type = cond do + String.contains?(media_type, "image") -> "image" + String.contains?(media_type, "video") -> "video" + true -> "unknown" + end + + %{ + id: attachment["uuid"], + url: href, + remote_url: href, + preview_url: href, + type: type + } + end +end diff --git a/lib/pleroma/web/oauth/app.ex b/lib/pleroma/web/oauth/app.ex new file mode 100644 index 000000000..ff52ba82e --- /dev/null +++ b/lib/pleroma/web/oauth/app.ex @@ -0,0 +1,29 @@ +defmodule Pleroma.Web.OAuth.App do + use Ecto.Schema + import Ecto.{Changeset} + + schema "apps" do + field :client_name, :string + field :redirect_uris, :string + field :scopes, :string + field :website, :string + field :client_id, :string + field :client_secret, :string + + timestamps() + end + + def register_changeset(struct, params \\ %{}) do + changeset = struct + |> cast(params, [:client_name, :redirect_uris, :scopes, :website]) + |> validate_required([:client_name, :redirect_uris, :scopes]) + + if changeset.valid? do + changeset + |> put_change(:client_id, :crypto.strong_rand_bytes(32) |> Base.url_encode64) + |> put_change(:client_secret, :crypto.strong_rand_bytes(32) |> Base.url_encode64) + else + changeset + end + end +end diff --git a/lib/pleroma/web/oauth/authorization.ex b/lib/pleroma/web/oauth/authorization.ex new file mode 100644 index 000000000..1ba5be602 --- /dev/null +++ b/lib/pleroma/web/oauth/authorization.ex @@ -0,0 +1,47 @@ +defmodule Pleroma.Web.OAuth.Authorization do + use Ecto.Schema + + alias Pleroma.{User, Repo} + alias Pleroma.Web.OAuth.{Authorization, App} + + import Ecto.{Changeset} + + schema "oauth_authorizations" do + field :token, :string + field :valid_until, :naive_datetime + field :used, :boolean, default: false + belongs_to :user, Pleroma.User + belongs_to :app, Pleroma.App + + timestamps() + end + + def create_authorization(%App{} = app, %User{} = user) do + token = :crypto.strong_rand_bytes(32) |> Base.url_encode64 + + authorization = %Authorization{ + token: token, + used: false, + user_id: user.id, + app_id: app.id, + valid_until: NaiveDateTime.add(NaiveDateTime.utc_now, 60 * 10) + } + + Repo.insert(authorization) + end + + def use_changeset(%Authorization{} = auth, params) do + auth + |> cast(params, [:used]) + |> validate_required([:used]) + end + + def use_token(%Authorization{used: false, valid_until: valid_until} = auth) do + if NaiveDateTime.diff(NaiveDateTime.utc_now, valid_until) < 0 do + Repo.update(use_changeset(auth, %{used: true})) + else + {:error, "token expired"} + end + end + def use_token(%Authorization{used: true}), do: {:error, "already used"} +end diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex new file mode 100644 index 000000000..4672ce00e --- /dev/null +++ b/lib/pleroma/web/oauth/oauth_controller.ex @@ -0,0 +1,49 @@ +defmodule Pleroma.Web.OAuth.OAuthController do + use Pleroma.Web, :controller + + alias Pleroma.Web.OAuth.{Authorization, Token, App} + alias Pleroma.{Repo, User} + alias Comeonin.Pbkdf2 + + def authorize(conn, params) do + render conn, "show.html", %{ + response_type: params["response_type"], + client_id: params["client_id"], + scope: params["scope"], + redirect_uri: params["redirect_uri"] + } + end + + def create_authorization(conn, %{"authorization" => %{"name" => name, "password" => password, "client_id" => client_id, "redirect_uri" => redirect_uri}} = params) do + with %User{} = user <- User.get_cached_by_nickname(name), + true <- Pbkdf2.checkpw(password, user.password_hash), + %App{} = app <- Repo.get_by(App, client_id: client_id), + {:ok, auth} <- Authorization.create_authorization(app, user) do + if redirect_uri == "urn:ietf:wg:oauth:2.0:oob" do + render conn, "results.html", %{ + auth: auth + } + else + url = "#{redirect_uri}?code=#{auth.token}" + redirect(conn, external: url) + end + end + end + + # TODO + # - proper scope handling + def token_exchange(conn, %{"grant_type" => "authorization_code"} = params) do + with %App{} = app <- Repo.get_by(App, client_id: params["client_id"], client_secret: params["client_secret"]), + %Authorization{} = auth <- Repo.get_by(Authorization, token: params["code"], app_id: app.id), + {:ok, token} <- Token.exchange_token(app, auth) do + response = %{ + token_type: "Bearer", + access_token: token.token, + refresh_token: token.refresh_token, + expires_in: 60 * 10, + scope: "read write follow" + } + json(conn, response) + end + end +end diff --git a/lib/pleroma/web/oauth/oauth_view.ex b/lib/pleroma/web/oauth/oauth_view.ex new file mode 100644 index 000000000..b3923fcf5 --- /dev/null +++ b/lib/pleroma/web/oauth/oauth_view.ex @@ -0,0 +1,4 @@ +defmodule Pleroma.Web.OAuth.OAuthView do + use Pleroma.Web, :view + import Phoenix.HTML.Form +end diff --git a/lib/pleroma/web/oauth/token.ex b/lib/pleroma/web/oauth/token.ex new file mode 100644 index 000000000..828a966fb --- /dev/null +++ b/lib/pleroma/web/oauth/token.ex @@ -0,0 +1,38 @@ +defmodule Pleroma.Web.OAuth.Token do + use Ecto.Schema + + alias Pleroma.{User, Repo} + alias Pleroma.Web.OAuth.{Token, App, Authorization} + + schema "oauth_tokens" do + field :token, :string + field :refresh_token, :string + field :valid_until, :naive_datetime + belongs_to :user, Pleroma.User + belongs_to :app, Pleroma.App + + timestamps() + end + + def exchange_token(app, auth) do + with {:ok, auth} <- Authorization.use_token(auth), + true <- auth.app_id == app.id do + create_token(app, Repo.get(User, auth.user_id)) + end + end + + def create_token(%App{} = app, %User{} = user) do + token = :crypto.strong_rand_bytes(32) |> Base.url_encode64 + refresh_token = :crypto.strong_rand_bytes(32) |> Base.url_encode64 + + token = %Token{ + token: token, + refresh_token: refresh_token, + user_id: user.id, + app_id: app.id, + valid_until: NaiveDateTime.add(NaiveDateTime.utc_now, 60 * 10) + } + + Repo.insert(token) + end +end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index c20ec3e80..161635558 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -10,12 +10,14 @@ defmodule Pleroma.Web.Router do pipeline :api do plug :accepts, ["json"] plug :fetch_session + plug Pleroma.Plugs.OAuthPlug plug Pleroma.Plugs.AuthenticationPlug, %{fetcher: &Router.user_fetcher/1, optional: true} end pipeline :authenticated_api do plug :accepts, ["json"] plug :fetch_session + plug Pleroma.Plugs.OAuthPlug plug Pleroma.Plugs.AuthenticationPlug, %{fetcher: &Router.user_fetcher/1} end @@ -27,14 +29,43 @@ defmodule Pleroma.Web.Router do plug :accepts, ["json", "xml"] end - pipeline :masto_config do - plug :accepts, ["json"] + pipeline :oauth do + plug :accepts, ["html", "json"] + end + + scope "/oauth", Pleroma.Web.OAuth do + get "/authorize", OAuthController, :authorize + post "/authorize", OAuthController, :create_authorization + post "/token", OAuthController, :token_exchange + end + + scope "/api/v1", Pleroma.Web.MastodonAPI do + pipe_through :api + get "/instance", MastodonAPIController, :masto_instance + post "/apps", MastodonAPIController, :create_app + + get "/timelines/public", MastodonAPIController, :public_timeline + + get "/statuses/:id", MastodonAPIController, :get_status + get "/statuses/:id/context", MastodonAPIController, :get_context + + get "/accounts/:id/statuses", MastodonAPIController, :user_statuses end - scope "/api/v1", Pleroma.Web do - pipe_through :masto_config - # TODO: Move this - get "/instance", TwitterAPI.UtilController, :masto_instance + scope "/api/v1", Pleroma.Web.MastodonAPI do + pipe_through :authenticated_api + + get "/accounts/verify_credentials", MastodonAPIController, :verify_credentials + get "/timelines/home", MastodonAPIController, :home_timeline + + post "/statuses", MastodonAPIController, :post_status + delete "/statuses/:id", MastodonAPIController, :delete_status + + post "/statuses/:id/reblog", MastodonAPIController, :reblog_status + post "/statuses/:id/favourite", MastodonAPIController, :fav_status + post "/statuses/:id/unfavourite", MastodonAPIController, :unfav_status + + get "/notifications", MastodonAPIController, :notifications end scope "/api", Pleroma.Web do diff --git a/lib/pleroma/web/templates/layout/app.html.eex b/lib/pleroma/web/templates/layout/app.html.eex new file mode 100644 index 000000000..6cc3b7ac5 --- /dev/null +++ b/lib/pleroma/web/templates/layout/app.html.eex @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset=utf-8 /> + <title>Pleroma</title> + </head> + <body> + <h1>Welcome to Pleroma</h1> + <%= render @view_module, @view_template, assigns %> + </body> +</html> diff --git a/lib/pleroma/web/templates/o_auth/o_auth/results.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/results.html.eex new file mode 100644 index 000000000..8443d906b --- /dev/null +++ b/lib/pleroma/web/templates/o_auth/o_auth/results.html.eex @@ -0,0 +1,2 @@ +<h1>Successfully authorized</h1> +<h2>Token code is <%= @auth.token %></h2> diff --git a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex new file mode 100644 index 000000000..ce295ed05 --- /dev/null +++ b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex @@ -0,0 +1,14 @@ +<h2>OAuth Authorization</h2> +<%= form_for @conn, o_auth_path(@conn, :authorize), [as: "authorization"], fn f -> %> +<%= label f, :name, "Name" %> +<%= text_input f, :name %> +<br> +<%= label f, :password, "Password" %> +<%= password_input f, :password %> +<br> +<%= hidden_input f, :client_id, value: @client_id %> +<%= hidden_input f, :response_type, value: @response_type %> +<%= hidden_input f, :redirect_uri, value: @redirect_uri %> +<%= hidden_input f, :scope, value: @scope %> +<%= submit "Authorize" %> +<% end %> diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex index 285b4d105..41881e742 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex @@ -42,16 +42,4 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do _ -> json(conn, "Pleroma Dev") end end - - # TODO: Move this - def masto_instance(conn, _params) do - response = %{ - uri: Web.base_url, - title: Web.base_url, - description: "A Pleroma instance, an alternative fediverse server", - version: "dev" - } - - json(conn, response) - end end diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex index 1ae076e24..657823d1d 100644 --- a/lib/pleroma/web/twitter_api/twitter_api.ex +++ b/lib/pleroma/web/twitter_api/twitter_api.ex @@ -3,7 +3,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter alias Pleroma.Web.TwitterAPI.UserView - alias Pleroma.Web.OStatus + alias Pleroma.Web.{OStatus, CommonAPI} alias Pleroma.Formatter import Pleroma.Web.TwitterAPI.Utils @@ -115,43 +115,28 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do end end - def favorite(%User{} = user, %Activity{data: %{"object" => object}} = activity) do - object = Object.get_by_ap_id(object["id"]) - - {:ok, _like_activity, object} = ActivityPub.like(user, object) - new_data = activity.data - |> Map.put("object", object.data) - - status = %{activity | data: new_data} - |> activity_to_status(%{for: user}) - - {:ok, status} + def repeat(%User{} = user, ap_id_or_id) do + with {:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.repeat(ap_id_or_id, user), + %Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id), + status <- activity_to_status(activity, %{for: user}) do + {:ok, status} + end end - def unfavorite(%User{} = user, %Activity{data: %{"object" => object}} = activity) do - object = Object.get_by_ap_id(object["id"]) - - {:ok, object} = ActivityPub.unlike(user, object) - new_data = activity.data - |> Map.put("object", object.data) - - status = %{activity | data: new_data} - |> activity_to_status(%{for: user}) - - {:ok, status} + def fav(%User{} = user, ap_id_or_id) do + with {:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.favorite(ap_id_or_id, user), + %Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id), + status <- activity_to_status(activity, %{for: user}) do + {:ok, status} + end end - def retweet(%User{} = user, %Activity{data: %{"object" => object}} = activity) do - object = Object.get_by_ap_id(object["id"]) - - {:ok, _announce_activity, object} = ActivityPub.announce(user, object) - new_data = activity.data - |> Map.put("object", object.data) - - status = %{activity | data: new_data} - |> activity_to_status(%{for: user}) - - {:ok, status} + def unfav(%User{} = user, ap_id_or_id) do + with {:ok, %{data: %{"id" => id}}} = CommonAPI.unfavorite(ap_id_or_id, user), + %Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id), + status <- activity_to_status(activity, %{for: user}) do + {:ok, status} + end end def upload(%Plug.Upload{} = file, format \\ "xml") do diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/twitter_api_controller.ex index 3ec54616a..62a2b4f50 100644 --- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex +++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex @@ -2,6 +2,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do use Pleroma.Web, :controller alias Pleroma.Web.TwitterAPI.{TwitterAPI, UserView} alias Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter + alias Pleroma.Web.CommonAPI alias Pleroma.{Repo, Activity, User, Object} alias Pleroma.Web.ActivityPub.ActivityPub alias Ecto.Changeset @@ -95,10 +96,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do end def delete_post(%{assigns: %{user: user}} = conn, %{"id" => id}) do - with %Activity{data: %{"object" => %{"id" => object_id}}} <- Repo.get(Activity, id), - %Object{} = object <- Object.get_by_ap_id(object_id), - true <- user.ap_id == object.data["actor"], - {:ok, delete} <- ActivityPub.delete(object) |> IO.inspect do + with {:ok, delete} <- CommonAPI.delete(id, user) do json = ActivityRepresenter.to_json(delete, %{user: user, for: user}) conn |> json_reply(200, json) @@ -151,40 +149,25 @@ defmodule Pleroma.Web.TwitterAPI.Controller do end def favorite(%{assigns: %{user: user}} = conn, %{"id" => id}) do - activity = get_by_id_or_ap_id(id) - {:ok, status} = TwitterAPI.favorite(user, activity) - response = Poison.encode!(status) - - conn - |> json_reply(200, response) + with {:ok, status} <- TwitterAPI.fav(user, id) do + json(conn, status) + end end def unfavorite(%{assigns: %{user: user}} = conn, %{"id" => id}) do - activity = get_by_id_or_ap_id(id) - {:ok, status} = TwitterAPI.unfavorite(user, activity) - response = Poison.encode!(status) - - conn - |> json_reply(200, response) + with {:ok, status} <- TwitterAPI.unfav(user, id) do + json(conn, status) + end end def retweet(%{assigns: %{user: user}} = conn, %{"id" => id}) do - activity = get_by_id_or_ap_id(id) - if activity.data["actor"] == user.ap_id do - bad_request_reply(conn, "You cannot repeat your own notice.") - else - {:ok, status} = TwitterAPI.retweet(user, activity) - response = Poison.encode!(status) - - conn - - |> json_reply(200, response) + with {:ok, status} <- TwitterAPI.repeat(user, id) do + json(conn, status) end end def register(conn, params) do with {:ok, user} <- TwitterAPI.register_user(params) do - render(conn, UserView, "show.json", %{user: user}) else {:error, errors} -> diff --git a/lib/pleroma/web/views/layout_view.ex b/lib/pleroma/web/views/layout_view.ex new file mode 100644 index 000000000..d4d4c3bd3 --- /dev/null +++ b/lib/pleroma/web/views/layout_view.ex @@ -0,0 +1,3 @@ +defmodule Pleroma.Web.LayoutView do + use Pleroma.Web, :view +end |