Merge conflict in test/web/mastodon_api/mastodon_api_controller_test.exstags/v1.1.4
@@ -124,6 +124,11 @@ config :logger, :ex_syslogger, | |||
format: "$metadata[$level] $message", | |||
metadata: [:request_id] | |||
config :quack, | |||
level: :warn, | |||
meta: [:all], | |||
webhook_url: "https://hooks.slack.com/services/YOUR-KEY-HERE" | |||
config :mime, :types, %{ | |||
"application/xml" => ["xml"], | |||
"application/xrd+xml" => ["xrd+xml"], | |||
@@ -58,6 +58,26 @@ Authentication is required and the user must be an admin. | |||
- `password` | |||
- Response: User’s nickname | |||
## `/api/pleroma/admin/user/follow` | |||
### Make a user follow another user | |||
- Methods: `POST` | |||
- Params: | |||
- `follower`: The nickname of the follower | |||
- `followed`: The nickname of the followed | |||
- Response: | |||
- "ok" | |||
## `/api/pleroma/admin/user/unfollow` | |||
### Make a user unfollow another user | |||
- Methods: `POST` | |||
- Params: | |||
- `follower`: The nickname of the follower | |||
- `followed`: The nickname of the followed | |||
- Response: | |||
- "ok" | |||
## `/api/pleroma/admin/users/:nickname/toggle_activation` | |||
### Toggle user activation | |||
@@ -44,3 +44,9 @@ Has these additional fields under the `pleroma` object: | |||
Has these additional fields under the `pleroma` object: | |||
- `is_seen`: true if the notification was read by the user | |||
## POST `/api/v1/statuses` | |||
Additional parameters can be added to the JSON body/Form data: | |||
- `preview`: boolean, if set to `true` the post won't be actually posted, but the status entitiy would still be rendered back. This could be useful for previewing rich text/custom emoji, for example. |
@@ -105,7 +105,7 @@ config :pleroma, Pleroma.Mailer, | |||
* `safe_dm_mentions`: If set to true, only mentions at the beginning of a post will be used to address people in direct messages. This is to prevent accidental mentioning of people when talking about them (e.g. "@friend hey i really don't like @enemy"). (Default: `false`) | |||
## :logger | |||
* `backends`: `:console` is used to send logs to stdout, `{ExSyslogger, :ex_syslogger}` to log to syslog | |||
* `backends`: `:console` is used to send logs to stdout, `{ExSyslogger, :ex_syslogger}` to log to syslog, and `Quack.Logger` to log to Slack | |||
An example to enable ONLY ExSyslogger (f/ex in ``prod.secret.exs``) with info and debug suppressed: | |||
``` | |||
@@ -128,6 +128,24 @@ config :logger, :ex_syslogger, | |||
See: [logger’s documentation](https://hexdocs.pm/logger/Logger.html) and [ex_syslogger’s documentation](https://hexdocs.pm/ex_syslogger/) | |||
An example of logging info to local syslog, but warn to a Slack channel: | |||
``` | |||
config :logger, | |||
backends: [ {ExSyslogger, :ex_syslogger}, Quack.Logger ], | |||
level: :info | |||
config :logger, :ex_syslogger, | |||
level: :info, | |||
ident: "pleroma", | |||
format: "$metadata[$level] $message" | |||
config :quack, | |||
level: :warn, | |||
meta: [:all], | |||
webhook_url: "https://hooks.slack.com/services/YOUR-API-KEY-HERE" | |||
``` | |||
See the [Quack Github](https://github.com/azohra/quack) for more details | |||
## :frontend_configurations | |||
@@ -81,6 +81,14 @@ defmodule Mix.Tasks.Pleroma.Instance do | |||
email = Common.get_option(options, :admin_email, "What is your admin email address?") | |||
indexable = | |||
Common.get_option( | |||
options, | |||
:indexable, | |||
"Do you want search engines to index your site? (y/n)", | |||
"y" | |||
) === "y" | |||
dbhost = | |||
Common.get_option(options, :dbhost, "What is the hostname of your database?", "localhost") | |||
@@ -142,6 +150,8 @@ defmodule Mix.Tasks.Pleroma.Instance do | |||
Mix.shell().info("Writing #{psql_path}.") | |||
File.write(psql_path, result_psql) | |||
write_robots_txt(indexable) | |||
Mix.shell().info( | |||
"\n" <> | |||
""" | |||
@@ -163,4 +173,28 @@ defmodule Mix.Tasks.Pleroma.Instance do | |||
) | |||
end | |||
end | |||
defp write_robots_txt(indexable) do | |||
robots_txt = | |||
EEx.eval_file( | |||
Path.expand("robots_txt.eex", __DIR__), | |||
indexable: indexable | |||
) | |||
static_dir = Pleroma.Config.get([:instance, :static_dir], "instance/static/") | |||
unless File.exists?(static_dir) do | |||
File.mkdir_p!(static_dir) | |||
end | |||
robots_txt_path = Path.join(static_dir, "robots.txt") | |||
if File.exists?(robots_txt_path) do | |||
File.cp!(robots_txt_path, "#{robots_txt_path}.bak") | |||
Mix.shell().info("Backing up existing robots.txt to #{robots_txt_path}.bak") | |||
end | |||
File.write(robots_txt_path, robots_txt) | |||
Mix.shell().info("Writing #{robots_txt_path}.") | |||
end | |||
end |
@@ -0,0 +1,2 @@ | |||
User-Agent: * | |||
Disallow: <%= if indexable, do: "", else: "/" %> |
@@ -28,27 +28,39 @@ defmodule Pleroma.HTML do | |||
def filter_tags(html), do: filter_tags(html, nil) | |||
def strip_tags(html), do: Scrubber.scrub(html, Scrubber.StripTags) | |||
def get_cached_scrubbed_html_for_object(content, scrubbers, object, module) do | |||
key = "#{module}#{generate_scrubber_signature(scrubbers)}|#{object.id}" | |||
Cachex.fetch!(:scrubber_cache, key, fn _key -> ensure_scrubbed_html(content, scrubbers) end) | |||
def get_cached_scrubbed_html_for_activity(content, scrubbers, activity, key \\ "") do | |||
key = "#{key}#{generate_scrubber_signature(scrubbers)}|#{activity.id}" | |||
Cachex.fetch!(:scrubber_cache, key, fn _key -> | |||
ensure_scrubbed_html(content, scrubbers, activity.data["object"]["fake"] || false) | |||
end) | |||
end | |||
def get_cached_stripped_html_for_object(content, object, module) do | |||
get_cached_scrubbed_html_for_object( | |||
def get_cached_stripped_html_for_activity(content, activity, key) do | |||
get_cached_scrubbed_html_for_activity( | |||
content, | |||
HtmlSanitizeEx.Scrubber.StripTags, | |||
object, | |||
module | |||
activity, | |||
key | |||
) | |||
end | |||
def ensure_scrubbed_html( | |||
content, | |||
scrubbers | |||
scrubbers, | |||
false = _fake | |||
) do | |||
{:commit, filter_tags(content, scrubbers)} | |||
end | |||
def ensure_scrubbed_html( | |||
content, | |||
scrubbers, | |||
true = _fake | |||
) do | |||
{:ignore, filter_tags(content, scrubbers)} | |||
end | |||
defp generate_scrubber_signature(scrubber) when is_atom(scrubber) do | |||
generate_scrubber_signature([scrubber]) | |||
end | |||
@@ -44,6 +44,11 @@ defmodule Pleroma.Object do | |||
# Use this whenever possible, especially when walking graphs in an O(N) loop! | |||
def normalize(%Activity{object: %Object{} = object}), do: object | |||
# A hack for fake activities | |||
def normalize(%Activity{data: %{"object" => %{"fake" => true} = data}}) do | |||
%Object{id: "pleroma:fake_object_id", data: data} | |||
end | |||
# Catch and log Object.normalize() calls where the Activity's child object is not | |||
# preloaded. | |||
def normalize(%Activity{data: %{"object" => %{"id" => ap_id}}}) do | |||
@@ -113,15 +113,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do | |||
def decrease_replies_count_if_reply(_object), do: :noop | |||
def insert(map, local \\ true) when is_map(map) do | |||
def insert(map, local \\ true, fake \\ false) when is_map(map) do | |||
with nil <- Activity.normalize(map), | |||
map <- lazy_put_activity_defaults(map), | |||
map <- lazy_put_activity_defaults(map, fake), | |||
:ok <- check_actor_is_active(map["actor"]), | |||
{_, true} <- {:remote_limit_error, check_remote_limit(map)}, | |||
{:ok, map} <- MRF.filter(map), | |||
{recipients, _, _} = get_recipients(map), | |||
{:fake, false, map, recipients} <- {:fake, fake, map, recipients}, | |||
{:ok, object} <- insert_full_object(map) do | |||
{recipients, _, _} = get_recipients(map) | |||
{:ok, activity} = | |||
Repo.insert(%Activity{ | |||
data: map, | |||
@@ -146,8 +146,23 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do | |||
stream_out(activity) | |||
{:ok, activity} | |||
else | |||
%Activity{} = activity -> {:ok, activity} | |||
error -> {:error, error} | |||
%Activity{} = activity -> | |||
{:ok, activity} | |||
{:fake, true, map, recipients} -> | |||
activity = %Activity{ | |||
data: map, | |||
local: local, | |||
actor: map["actor"], | |||
recipients: recipients, | |||
id: "pleroma:fakeid" | |||
} | |||
Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) | |||
{:ok, activity} | |||
error -> | |||
{:error, error} | |||
end | |||
end | |||
@@ -190,7 +205,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do | |||
end | |||
end | |||
def create(%{to: to, actor: actor, context: context, object: object} = params) do | |||
def create(%{to: to, actor: actor, context: context, object: object} = params, fake \\ false) do | |||
additional = params[:additional] || %{} | |||
# only accept false as false value | |||
local = !(params[:local] == false) | |||
@@ -201,13 +216,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do | |||
%{to: to, actor: actor, published: published, context: context, object: object}, | |||
additional | |||
), | |||
{:ok, activity} <- insert(create_data, local), | |||
{:ok, activity} <- insert(create_data, local, fake), | |||
{:fake, false, activity} <- {:fake, fake, activity}, | |||
_ <- increase_replies_count_if_reply(create_data), | |||
# Changing note count prior to enqueuing federation task in order to avoid | |||
# race conditions on updating user.info | |||
{:ok, _actor} <- increase_note_count_if_public(actor, activity), | |||
:ok <- maybe_federate(activity) do | |||
{:ok, activity} | |||
else | |||
{:fake, true, activity} -> | |||
{:ok, activity} | |||
end | |||
end | |||
@@ -175,18 +175,26 @@ defmodule Pleroma.Web.ActivityPub.Utils do | |||
Adds an id and a published data if they aren't there, | |||
also adds it to an included object | |||
""" | |||
def lazy_put_activity_defaults(map) do | |||
%{data: %{"id" => context}, id: context_id} = create_context(map["context"]) | |||
def lazy_put_activity_defaults(map, fake \\ false) do | |||
map = | |||
map | |||
|> Map.put_new_lazy("id", &generate_activity_id/0) | |||
|> Map.put_new_lazy("published", &make_date/0) | |||
|> Map.put_new("context", context) | |||
|> Map.put_new("context_id", context_id) | |||
unless fake do | |||
%{data: %{"id" => context}, id: context_id} = create_context(map["context"]) | |||
map | |||
|> Map.put_new_lazy("id", &generate_activity_id/0) | |||
|> Map.put_new_lazy("published", &make_date/0) | |||
|> Map.put_new("context", context) | |||
|> Map.put_new("context_id", context_id) | |||
else | |||
map | |||
|> Map.put_new("id", "pleroma:fakeid") | |||
|> Map.put_new_lazy("published", &make_date/0) | |||
|> Map.put_new("context", "pleroma:fakecontext") | |||
|> Map.put_new("context_id", -1) | |||
end | |||
if is_map(map["object"]) do | |||
object = lazy_put_object_defaults(map["object"], map) | |||
object = lazy_put_object_defaults(map["object"], map, fake) | |||
%{map | "object" => object} | |||
else | |||
map | |||
@@ -196,7 +204,18 @@ defmodule Pleroma.Web.ActivityPub.Utils do | |||
@doc """ | |||
Adds an id and published date if they aren't there. | |||
""" | |||
def lazy_put_object_defaults(map, activity \\ %{}) do | |||
def lazy_put_object_defaults(map, activity \\ %{}, fake) | |||
def lazy_put_object_defaults(map, activity, true = _fake) do | |||
map | |||
|> Map.put_new_lazy("published", &make_date/0) | |||
|> Map.put_new("id", "pleroma:fake_object_id") | |||
|> Map.put_new("context", activity["context"]) | |||
|> Map.put_new("fake", true) | |||
|> Map.put_new("context_id", activity["context_id"]) | |||
end | |||
def lazy_put_object_defaults(map, activity, _fake) do | |||
map | |||
|> Map.put_new_lazy("id", &generate_object_id/0) | |||
|> Map.put_new_lazy("published", &make_date/0) | |||
@@ -404,13 +423,15 @@ defmodule Pleroma.Web.ActivityPub.Utils do | |||
activity.data | |||
), | |||
where: activity.actor == ^follower_id, | |||
# this is to use the index | |||
where: | |||
fragment( | |||
"? @> ?", | |||
"coalesce((?)->'object'->>'id', (?)->>'object') = ?", | |||
activity.data, | |||
^%{object: followed_id} | |||
activity.data, | |||
^followed_id | |||
), | |||
order_by: [desc: :id], | |||
order_by: [fragment("? desc nulls last", activity.id)], | |||
limit: 1 | |||
) | |||
@@ -567,13 +588,15 @@ defmodule Pleroma.Web.ActivityPub.Utils do | |||
activity.data | |||
), | |||
where: activity.actor == ^blocker_id, | |||
# this is to use the index | |||
where: | |||
fragment( | |||
"? @> ?", | |||
"coalesce((?)->'object'->>'id', (?)->>'object') = ?", | |||
activity.data, | |||
activity.data, | |||
^%{object: blocked_id} | |||
^blocked_id | |||
), | |||
order_by: [desc: :id], | |||
order_by: [fragment("? desc nulls last", activity.id)], | |||
limit: 1 | |||
) | |||
@@ -25,6 +25,26 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do | |||
|> json(nickname) | |||
end | |||
def user_follow(conn, %{"follower" => follower_nick, "followed" => followed_nick}) do | |||
with %User{} = follower <- User.get_by_nickname(follower_nick), | |||
%User{} = followed <- User.get_by_nickname(followed_nick) do | |||
User.follow(follower, followed) | |||
end | |||
conn | |||
|> json("ok") | |||
end | |||
def user_unfollow(conn, %{"follower" => follower_nick, "followed" => followed_nick}) do | |||
with %User{} = follower <- User.get_by_nickname(follower_nick), | |||
%User{} = followed <- User.get_by_nickname(followed_nick) do | |||
User.unfollow(follower, followed) | |||
end | |||
conn | |||
|> json("ok") | |||
end | |||
def user_create( | |||
conn, | |||
%{"nickname" => nickname, "email" => email, "password" => password} | |||
@@ -172,13 +172,16 @@ defmodule Pleroma.Web.CommonAPI do | |||
end) | |||
) do | |||
res = | |||
ActivityPub.create(%{ | |||
to: to, | |||
actor: user, | |||
context: context, | |||
object: object, | |||
additional: %{"cc" => cc, "directMessage" => visibility == "direct"} | |||
}) | |||
ActivityPub.create( | |||
%{ | |||
to: to, | |||
actor: user, | |||
context: context, | |||
object: object, | |||
additional: %{"cc" => cc, "directMessage" => visibility == "direct"} | |||
}, | |||
Pleroma.Web.ControllerHelper.truthy_param?(data["preview"]) || false | |||
) | |||
res | |||
end | |||
@@ -15,6 +15,8 @@ defmodule Pleroma.Web.CommonAPI.Utils do | |||
alias Pleroma.Web.Endpoint | |||
alias Pleroma.Web.MediaProxy | |||
require Logger | |||
# This is a hack for twidere. | |||
def get_by_id_or_ap_id(id) do | |||
activity = | |||
@@ -240,15 +242,21 @@ defmodule Pleroma.Web.CommonAPI.Utils do | |||
Strftime.strftime!(date, "%a %b %d %H:%M:%S %z %Y") | |||
end | |||
def date_to_asctime(date) do | |||
with {:ok, date, _offset} <- date |> DateTime.from_iso8601() do | |||
def date_to_asctime(date) when is_binary(date) do | |||
with {:ok, date, _offset} <- DateTime.from_iso8601(date) do | |||
format_asctime(date) | |||
else | |||
_e -> | |||
Logger.warn("Date #{date} in wrong format, must be ISO 8601") | |||
"" | |||
end | |||
end | |||
def date_to_asctime(date) do | |||
Logger.warn("Date #{date} in wrong format, must be ISO 8601") | |||
"" | |||
end | |||
def to_masto_date(%NaiveDateTime{} = date) do | |||
date | |||
|> NaiveDateTime.to_iso8601() | |||
@@ -1092,9 +1092,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do | |||
end | |||
def index(%{assigns: %{user: user}} = conn, _params) do | |||
token = | |||
conn | |||
|> get_session(:oauth_token) | |||
token = get_session(conn, :oauth_token) | |||
if user && token do | |||
mastodon_emoji = mastodonized_emoji() | |||
@@ -1122,7 +1120,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do | |||
auto_play_gif: false, | |||
display_sensitive_media: false, | |||
reduce_motion: false, | |||
max_toot_chars: limit | |||
max_toot_chars: limit, | |||
mascot: "/images/pleroma-fox-tan-smol.png" | |||
}, | |||
rights: %{ | |||
delete_others_notice: present?(user.info.is_moderator), | |||
@@ -1194,6 +1193,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do | |||
|> render("index.html", %{initial_state: initial_state, flavour: flavour}) | |||
else | |||
conn | |||
|> put_session(:return_to, conn.request_path) | |||
|> redirect(to: "/web/login") | |||
end | |||
end | |||
@@ -1278,12 +1278,20 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do | |||
scope: Enum.join(app.scopes, " ") | |||
) | |||
conn | |||
|> redirect(to: path) | |||
redirect(conn, to: path) | |||
end | |||
end | |||
defp local_mastodon_root_path(conn), do: mastodon_api_path(conn, :index, ["getting-started"]) | |||
defp local_mastodon_root_path(conn) do | |||
case get_session(conn, :return_to) do | |||
nil -> | |||
mastodon_api_path(conn, :index, ["getting-started"]) | |||
return_to -> | |||
delete_session(conn, :return_to) | |||
return_to | |||
end | |||
end | |||
defp get_or_make_app do | |||
find_attrs = %{client_name: @local_mastodon_name, redirect_uris: "."} | |||
@@ -147,10 +147,18 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do | |||
content = | |||
object | |||
|> render_content() | |||
|> HTML.get_cached_scrubbed_html_for_object( | |||
|> HTML.get_cached_scrubbed_html_for_activity( | |||
User.html_filter_policy(opts[:for]), | |||
activity, | |||
__MODULE__ | |||
"mastoapi:content" | |||
) | |||
summary = | |||
(object["summary"] || "") | |||
|> HTML.get_cached_scrubbed_html_for_activity( | |||
User.html_filter_policy(opts[:for]), | |||
activity, | |||
"mastoapi:summary" | |||
) | |||
card = render("card.json", Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)) | |||
@@ -182,7 +190,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do | |||
muted: CommonAPI.thread_muted?(user, activity) || User.mutes?(opts[:for], user), | |||
pinned: pinned?(activity, user), | |||
sensitive: sensitive, | |||
spoiler_text: object["summary"] || "", | |||
spoiler_text: summary, | |||
visibility: get_visibility(object), | |||
media_attachments: attachments, | |||
mentions: mentions, | |||
@@ -12,7 +12,7 @@ defmodule Pleroma.Web.Metadata.Utils do | |||
# html content comes from DB already encoded, decode first and scrub after | |||
|> HtmlEntities.decode() | |||
|> String.replace(~r/<br\s?\/?>/, " ") | |||
|> HTML.get_cached_stripped_html_for_object(object, __MODULE__) | |||
|> HTML.get_cached_stripped_html_for_activity(object, "metadata") | |||
|> Formatter.demojify() | |||
|> Formatter.truncate() | |||
end | |||
@@ -140,8 +140,12 @@ defmodule Pleroma.Web.Router do | |||
scope "/api/pleroma/admin", Pleroma.Web.AdminAPI do | |||
pipe_through([:admin_api, :oauth_write]) | |||
post("/user/follow", AdminAPIController, :user_follow) | |||
post("/user/unfollow", AdminAPIController, :user_unfollow) | |||
get("/users", AdminAPIController, :list_users) | |||
get("/users/:nickname", AdminAPIController, :user_show) | |||
delete("/user", AdminAPIController, :user_delete) | |||
patch("/users/:nickname/toggle_activation", AdminAPIController, :user_toggle_activation) | |||
post("/user", AdminAPIController, :user_create) | |||
@@ -254,10 +254,10 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do | |||
html = | |||
content | |||
|> HTML.get_cached_scrubbed_html_for_object( | |||
|> HTML.get_cached_scrubbed_html_for_activity( | |||
User.html_filter_policy(opts[:for]), | |||
activity, | |||
__MODULE__ | |||
"twitterapi:content" | |||
) | |||
|> Formatter.emojify(object["emoji"]) | |||
@@ -265,7 +265,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do | |||
if content do | |||
content | |||
|> String.replace(~r/<br\s?\/?>/, "\n") | |||
|> HTML.get_cached_stripped_html_for_object(activity, __MODULE__) | |||
|> HTML.get_cached_stripped_html_for_activity(activity, "twitterapi:content") | |||
else | |||
"" | |||
end | |||
@@ -41,7 +41,7 @@ defmodule Pleroma.Mixfile do | |||
def application do | |||
[ | |||
mod: {Pleroma.Application, []}, | |||
extra_applications: [:logger, :runtime_tools, :comeonin], | |||
extra_applications: [:logger, :runtime_tools, :comeonin, :quack], | |||
included_applications: [:ex_syslogger] | |||
] | |||
end | |||
@@ -93,8 +93,9 @@ defmodule Pleroma.Mixfile do | |||
{:timex, "~> 3.5"}, | |||
{:auto_linker, | |||
git: "https://git.pleroma.social/pleroma/auto_linker.git", | |||
ref: "94193ca5f97c1f9fdf3d1469653e2d46fac34bcd"}, | |||
{:pleroma_job_queue, "~> 0.2.0"} | |||
ref: "479dd343f4e563ff91215c8275f3b5c67e032850"}, | |||
{:pleroma_job_queue, "~> 0.2.0"}, | |||
{:quack, "~> 0.1.1"} | |||
] | |||
end | |||
@@ -1,5 +1,5 @@ | |||
%{ | |||
"auto_linker": {:git, "https://git.pleroma.social/pleroma/auto_linker.git", "94193ca5f97c1f9fdf3d1469653e2d46fac34bcd", [ref: "94193ca5f97c1f9fdf3d1469653e2d46fac34bcd"]}, | |||
"auto_linker": {:git, "https://git.pleroma.social/pleroma/auto_linker.git", "479dd343f4e563ff91215c8275f3b5c67e032850", [ref: "479dd343f4e563ff91215c8275f3b5c67e032850"]}, | |||
"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"}, | |||
@@ -57,6 +57,7 @@ | |||
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"}, | |||
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm"}, | |||
"postgrex": {:hex, :postgrex, "0.14.1", "63247d4a5ad6b9de57a0bac5d807e1c32d41e39c04b8a4156a26c63bcd8a2e49", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"}, | |||
"quack": {:hex, :quack, "0.1.1", "cca7b4da1a233757fdb44b3334fce80c94785b3ad5a602053b7a002b5a8967bf", [:mix], [{:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: false]}, {:tesla, "~> 1.2.0", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm"}, | |||
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"}, | |||
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], [], "hexpm"}, | |||
"swoosh": {:hex, :swoosh, "0.20.0", "9a6c13822c9815993c03b6f8fccc370fcffb3c158d9754f67b1fdee6b3a5d928", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.12", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 1.1", [hex: :mime, repo: "hexpm", optional: false]}, {:plug, "~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm"}, | |||
@@ -0,0 +1,9 @@ | |||
defmodule Pleroma.Repo.Migrations.AddOauthTokenIndexes do | |||
use Ecto.Migration | |||
def change do | |||
create(unique_index(:oauth_tokens, [:token])) | |||
create(index(:oauth_tokens, [:app_id])) | |||
create(index(:oauth_tokens, [:user_id])) | |||
end | |||
end |
@@ -240,6 +240,16 @@ defmodule Pleroma.Factory do | |||
} | |||
end | |||
def oauth_authorization_factory do | |||
%Pleroma.Web.OAuth.Authorization{ | |||
token: :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false), | |||
scopes: ["read", "write", "follow", "push"], | |||
valid_until: NaiveDateTime.add(NaiveDateTime.utc_now(), 60 * 10), | |||
user: build(:user), | |||
app: build(:oauth_app) | |||
} | |||
end | |||
def push_subscription_factory do | |||
%Pleroma.Web.Push.Subscription{ | |||
user: build(:user), | |||
@@ -635,16 +635,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do | |||
end | |||
end | |||
describe "fetch the latest Follow" do | |||
test "fetches the latest Follow activity" do | |||
%Activity{data: %{"type" => "Follow"}} = activity = insert(:follow_activity) | |||
follower = User.get_by_ap_id(activity.data["actor"]) | |||
followed = User.get_by_ap_id(activity.data["object"]) | |||
assert activity == Utils.fetch_latest_follow(follower, followed) | |||
end | |||
end | |||
describe "fetching an object" do | |||
test "it fetches an object" do | |||
{:ok, object} = | |||
@@ -1,10 +1,34 @@ | |||
defmodule Pleroma.Web.ActivityPub.UtilsTest do | |||
use Pleroma.DataCase | |||
alias Pleroma.Activity | |||
alias Pleroma.Repo | |||
alias Pleroma.User | |||
alias Pleroma.Web.ActivityPub.ActivityPub | |||
alias Pleroma.Web.ActivityPub.Utils | |||
alias Pleroma.Web.CommonAPI | |||
import Pleroma.Factory | |||
describe "fetch the latest Follow" do | |||
test "fetches the latest Follow activity" do | |||
%Activity{data: %{"type" => "Follow"}} = activity = insert(:follow_activity) | |||
follower = Repo.get_by(User, ap_id: activity.data["actor"]) | |||
followed = Repo.get_by(User, ap_id: activity.data["object"]) | |||
assert activity == Utils.fetch_latest_follow(follower, followed) | |||
end | |||
end | |||
describe "fetch the latest Block" do | |||
test "fetches the latest Block activity" do | |||
blocker = insert(:user) | |||
blocked = insert(:user) | |||
{:ok, activity} = ActivityPub.block(blocker, blocked) | |||
assert activity == Utils.fetch_latest_block(blocker, blocked) | |||
end | |||
end | |||
describe "determine_explicit_mentions()" do | |||
test "works with an object that has mentions" do | |||
object = %{ | |||
@@ -74,6 +74,52 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do | |||
end | |||
end | |||
describe "/api/pleroma/admin/user/follow" do | |||
test "allows to force-follow another user" do | |||
admin = insert(:user, info: %{is_admin: true}) | |||
user = insert(:user) | |||
follower = insert(:user) | |||
conn = | |||
build_conn() | |||
|> assign(:user, admin) | |||
|> put_req_header("accept", "application/json") | |||
|> post("/api/pleroma/admin/user/follow", %{ | |||
"follower" => follower.nickname, | |||
"followed" => user.nickname | |||
}) | |||
user = User.get_by_id(user.id) | |||
follower = User.get_by_id(follower.id) | |||
assert User.following?(follower, user) | |||
end | |||
end | |||
describe "/api/pleroma/admin/user/unfollow" do | |||
test "allows to force-unfollow another user" do | |||
admin = insert(:user, info: %{is_admin: true}) | |||
user = insert(:user) | |||
follower = insert(:user) | |||
User.follow(follower, user) | |||
conn = | |||
build_conn() | |||
|> assign(:user, admin) | |||
|> put_req_header("accept", "application/json") | |||
|> post("/api/pleroma/admin/user/unfollow", %{ | |||
"follower" => follower.nickname, | |||
"followed" => user.nickname | |||
}) | |||
user = User.get_by_id(user.id) | |||
follower = User.get_by_id(follower.id) | |||
refute User.following?(follower, user) | |||
end | |||
end | |||
describe "PUT /api/pleroma/admin/users/tag" do | |||
setup do | |||
admin = insert(:user, info: %{is_admin: true}) | |||
@@ -153,4 +153,40 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do | |||
assert conversation_id == object.id | |||
end | |||
end | |||
describe "formats date to asctime" do | |||
test "when date is in ISO 8601 format" do | |||
date = DateTime.utc_now() |> DateTime.to_iso8601() | |||
expected = | |||
date | |||
|> DateTime.from_iso8601() | |||
|> elem(1) | |||
|> Calendar.Strftime.strftime!("%a %b %d %H:%M:%S %z %Y") | |||
assert Utils.date_to_asctime(date) == expected | |||
end | |||
test "when date is a binary in wrong format" do | |||
date = DateTime.utc_now() | |||
expected = "" | |||
assert Utils.date_to_asctime(date) == expected | |||
end | |||
test "when date is a Unix timestamp" do | |||
date = DateTime.utc_now() |> DateTime.to_unix() | |||
expected = "" | |||
assert Utils.date_to_asctime(date) == expected | |||
end | |||
test "when date is nil" do | |||
expected = "" | |||
assert Utils.date_to_asctime(nil) == expected | |||
end | |||
end | |||
end |
@@ -143,6 +143,55 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do | |||
assert Activity.get_by_id(id) | |||
end | |||
test "posting a fake status", %{conn: conn} do | |||
user = insert(:user) | |||
real_conn = | |||
conn | |||
|> assign(:user, user) | |||
|> post("/api/v1/statuses", %{ | |||
"status" => | |||
"\"Tenshi Eating a Corndog\" is a much discussed concept on /jp/. The significance of it is disputed, so I will focus on one core concept: the symbolism behind it" | |||
}) | |||
real_status = json_response(real_conn, 200) | |||
assert real_status | |||
assert Object.get_by_ap_id(real_status["uri"]) | |||
real_status = | |||
real_status | |||
|> Map.put("id", nil) | |||
|> Map.put("url", nil) | |||
|> Map.put("uri", nil) | |||
|> Map.put("created_at", nil) | |||
|> Kernel.put_in(["pleroma", "conversation_id"], nil) | |||
fake_conn = | |||
conn | |||
|> assign(:user, user) | |||
|> post("/api/v1/statuses", %{ | |||
"status" => | |||
"\"Tenshi Eating a Corndog\" is a much discussed concept on /jp/. The significance of it is disputed, so I will focus on one core concept: the symbolism behind it", | |||
"preview" => true | |||
}) | |||
fake_status = json_response(fake_conn, 200) | |||
assert fake_status | |||
refute Object.get_by_ap_id(fake_status["uri"]) | |||
fake_status = | |||
fake_status | |||
|> Map.put("id", nil) | |||
|> Map.put("url", nil) | |||
|> Map.put("uri", nil) | |||
|> Map.put("created_at", nil) | |||
|> Kernel.put_in(["pleroma", "conversation_id"], nil) | |||
assert real_status == fake_status | |||
end | |||
test "posting a status with OGP link preview", %{conn: conn} do | |||
Pleroma.Config.put([:rich_media, :enabled], true) | |||
user = insert(:user) | |||
@@ -2307,4 +2356,71 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do | |||
assert Map.has_key?(emoji, "visible_in_picker") | |||
end | |||
end | |||
describe "index/2 redirections" do | |||
setup %{conn: conn} do | |||
session_opts = [ | |||
store: :cookie, | |||
key: "_test", | |||
signing_salt: "cooldude" | |||
] | |||
conn = | |||
conn | |||
|> Plug.Session.call(Plug.Session.init(session_opts)) | |||
|> fetch_session() | |||
test_path = "/web/statuses/test" | |||
%{conn: conn, path: test_path} | |||
end | |||
test "redirects not logged-in users to the login page", %{conn: conn, path: path} do | |||
conn = get(conn, path) | |||
assert conn.status == 302 | |||
assert redirected_to(conn) == "/web/login" | |||
end | |||
test "does not redirect logged in users to the login page", %{conn: conn, path: path} do | |||
token = insert(:oauth_token) | |||
conn = | |||
conn | |||
|> assign(:user, token.user) | |||
|> put_session(:oauth_token, token.token) | |||
|> get(path) | |||
assert conn.status == 200 | |||
end | |||
test "saves referer path to session", %{conn: conn, path: path} do | |||
conn = get(conn, path) | |||
return_to = Plug.Conn.get_session(conn, :return_to) | |||
assert return_to == path | |||
end | |||
test "redirects to the saved path after log in", %{conn: conn, path: path} do | |||
app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".") | |||
auth = insert(:oauth_authorization, app: app) | |||
conn = | |||
conn | |||
|> put_session(:return_to, path) | |||
|> get("/web/login", %{code: auth.token}) | |||
assert conn.status == 302 | |||
assert redirected_to(conn) == path | |||
end | |||
test "redirects to the getting-started page when referer is not present", %{conn: conn} do | |||
app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".") | |||
auth = insert(:oauth_authorization, app: app) | |||
conn = get(conn, "/web/login", %{code: auth.token}) | |||
assert conn.status == 302 | |||
assert redirected_to(conn) == "/web/getting-started" | |||
end | |||
end | |||
end |