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", | format: "$metadata[$level] $message", | ||||
metadata: [:request_id] | metadata: [:request_id] | ||||
config :quack, | |||||
level: :warn, | |||||
meta: [:all], | |||||
webhook_url: "https://hooks.slack.com/services/YOUR-KEY-HERE" | |||||
config :mime, :types, %{ | config :mime, :types, %{ | ||||
"application/xml" => ["xml"], | "application/xml" => ["xml"], | ||||
"application/xrd+xml" => ["xrd+xml"], | "application/xrd+xml" => ["xrd+xml"], | ||||
@@ -58,6 +58,26 @@ Authentication is required and the user must be an admin. | |||||
- `password` | - `password` | ||||
- Response: User’s nickname | - 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` | ## `/api/pleroma/admin/users/:nickname/toggle_activation` | ||||
### Toggle user activation | ### Toggle user activation | ||||
@@ -44,3 +44,9 @@ Has these additional fields under the `pleroma` object: | |||||
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 | - `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`) | * `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 | ## :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: | 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/) | 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 | ## :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?") | 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 = | dbhost = | ||||
Common.get_option(options, :dbhost, "What is the hostname of your database?", "localhost") | 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}.") | Mix.shell().info("Writing #{psql_path}.") | ||||
File.write(psql_path, result_psql) | File.write(psql_path, result_psql) | ||||
write_robots_txt(indexable) | |||||
Mix.shell().info( | Mix.shell().info( | ||||
"\n" <> | "\n" <> | ||||
""" | """ | ||||
@@ -163,4 +173,28 @@ defmodule Mix.Tasks.Pleroma.Instance do | |||||
) | ) | ||||
end | end | ||||
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 | 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 filter_tags(html), do: filter_tags(html, nil) | ||||
def strip_tags(html), do: Scrubber.scrub(html, Scrubber.StripTags) | 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 | 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, | content, | ||||
HtmlSanitizeEx.Scrubber.StripTags, | HtmlSanitizeEx.Scrubber.StripTags, | ||||
object, | |||||
module | |||||
activity, | |||||
key | |||||
) | ) | ||||
end | end | ||||
def ensure_scrubbed_html( | def ensure_scrubbed_html( | ||||
content, | content, | ||||
scrubbers | |||||
scrubbers, | |||||
false = _fake | |||||
) do | ) do | ||||
{:commit, filter_tags(content, scrubbers)} | {:commit, filter_tags(content, scrubbers)} | ||||
end | 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 | defp generate_scrubber_signature(scrubber) when is_atom(scrubber) do | ||||
generate_scrubber_signature([scrubber]) | generate_scrubber_signature([scrubber]) | ||||
end | end | ||||
@@ -44,6 +44,11 @@ defmodule Pleroma.Object do | |||||
# Use this whenever possible, especially when walking graphs in an O(N) loop! | # Use this whenever possible, especially when walking graphs in an O(N) loop! | ||||
def normalize(%Activity{object: %Object{} = object}), do: object | 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 | # Catch and log Object.normalize() calls where the Activity's child object is not | ||||
# preloaded. | # preloaded. | ||||
def normalize(%Activity{data: %{"object" => %{"id" => ap_id}}}) do | 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 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), | 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"]), | :ok <- check_actor_is_active(map["actor"]), | ||||
{_, true} <- {:remote_limit_error, check_remote_limit(map)}, | {_, true} <- {:remote_limit_error, check_remote_limit(map)}, | ||||
{:ok, map} <- MRF.filter(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 | {:ok, object} <- insert_full_object(map) do | ||||
{recipients, _, _} = get_recipients(map) | |||||
{:ok, activity} = | {:ok, activity} = | ||||
Repo.insert(%Activity{ | Repo.insert(%Activity{ | ||||
data: map, | data: map, | ||||
@@ -146,8 +146,23 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do | |||||
stream_out(activity) | stream_out(activity) | ||||
{:ok, activity} | {:ok, activity} | ||||
else | 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 | ||||
end | end | ||||
@@ -190,7 +205,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do | |||||
end | end | ||||
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] || %{} | additional = params[:additional] || %{} | ||||
# only accept false as false value | # only accept false as false value | ||||
local = !(params[:local] == false) | local = !(params[:local] == false) | ||||
@@ -201,13 +216,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do | |||||
%{to: to, actor: actor, published: published, context: context, object: object}, | %{to: to, actor: actor, published: published, context: context, object: object}, | ||||
additional | 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), | _ <- increase_replies_count_if_reply(create_data), | ||||
# Changing note count prior to enqueuing federation task in order to avoid | # Changing note count prior to enqueuing federation task in order to avoid | ||||
# race conditions on updating user.info | # race conditions on updating user.info | ||||
{:ok, _actor} <- increase_note_count_if_public(actor, activity), | {:ok, _actor} <- increase_note_count_if_public(actor, activity), | ||||
:ok <- maybe_federate(activity) do | :ok <- maybe_federate(activity) do | ||||
{:ok, activity} | {:ok, activity} | ||||
else | |||||
{:fake, true, activity} -> | |||||
{:ok, activity} | |||||
end | end | ||||
end | end | ||||
@@ -175,18 +175,26 @@ defmodule Pleroma.Web.ActivityPub.Utils do | |||||
Adds an id and a published data if they aren't there, | Adds an id and a published data if they aren't there, | ||||
also adds it to an included object | 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 | |||||
|> 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 | 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} | %{map | "object" => object} | ||||
else | else | ||||
map | map | ||||
@@ -196,7 +204,18 @@ defmodule Pleroma.Web.ActivityPub.Utils do | |||||
@doc """ | @doc """ | ||||
Adds an id and published date if they aren't there. | 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 | ||||
|> Map.put_new_lazy("id", &generate_object_id/0) | |> Map.put_new_lazy("id", &generate_object_id/0) | ||||
|> Map.put_new_lazy("published", &make_date/0) | |> Map.put_new_lazy("published", &make_date/0) | ||||
@@ -404,13 +423,15 @@ defmodule Pleroma.Web.ActivityPub.Utils do | |||||
activity.data | activity.data | ||||
), | ), | ||||
where: activity.actor == ^follower_id, | where: activity.actor == ^follower_id, | ||||
# this is to use the index | |||||
where: | where: | ||||
fragment( | fragment( | ||||
"? @> ?", | |||||
"coalesce((?)->'object'->>'id', (?)->>'object') = ?", | |||||
activity.data, | activity.data, | ||||
^%{object: followed_id} | |||||
activity.data, | |||||
^followed_id | |||||
), | ), | ||||
order_by: [desc: :id], | |||||
order_by: [fragment("? desc nulls last", activity.id)], | |||||
limit: 1 | limit: 1 | ||||
) | ) | ||||
@@ -567,13 +588,15 @@ defmodule Pleroma.Web.ActivityPub.Utils do | |||||
activity.data | activity.data | ||||
), | ), | ||||
where: activity.actor == ^blocker_id, | where: activity.actor == ^blocker_id, | ||||
# this is to use the index | |||||
where: | where: | ||||
fragment( | fragment( | ||||
"? @> ?", | |||||
"coalesce((?)->'object'->>'id', (?)->>'object') = ?", | |||||
activity.data, | |||||
activity.data, | activity.data, | ||||
^%{object: blocked_id} | |||||
^blocked_id | |||||
), | ), | ||||
order_by: [desc: :id], | |||||
order_by: [fragment("? desc nulls last", activity.id)], | |||||
limit: 1 | limit: 1 | ||||
) | ) | ||||
@@ -25,6 +25,26 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do | |||||
|> json(nickname) | |> json(nickname) | ||||
end | 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( | def user_create( | ||||
conn, | conn, | ||||
%{"nickname" => nickname, "email" => email, "password" => password} | %{"nickname" => nickname, "email" => email, "password" => password} | ||||
@@ -172,13 +172,16 @@ defmodule Pleroma.Web.CommonAPI do | |||||
end) | end) | ||||
) do | ) do | ||||
res = | 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 | res | ||||
end | end | ||||
@@ -15,6 +15,8 @@ defmodule Pleroma.Web.CommonAPI.Utils do | |||||
alias Pleroma.Web.Endpoint | alias Pleroma.Web.Endpoint | ||||
alias Pleroma.Web.MediaProxy | alias Pleroma.Web.MediaProxy | ||||
require Logger | |||||
# This is a hack for twidere. | # This is a hack for twidere. | ||||
def get_by_id_or_ap_id(id) do | def get_by_id_or_ap_id(id) do | ||||
activity = | activity = | ||||
@@ -240,15 +242,21 @@ defmodule Pleroma.Web.CommonAPI.Utils do | |||||
Strftime.strftime!(date, "%a %b %d %H:%M:%S %z %Y") | Strftime.strftime!(date, "%a %b %d %H:%M:%S %z %Y") | ||||
end | 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) | format_asctime(date) | ||||
else | else | ||||
_e -> | _e -> | ||||
Logger.warn("Date #{date} in wrong format, must be ISO 8601") | |||||
"" | "" | ||||
end | end | ||||
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 | def to_masto_date(%NaiveDateTime{} = date) do | ||||
date | date | ||||
|> NaiveDateTime.to_iso8601() | |> NaiveDateTime.to_iso8601() | ||||
@@ -1092,9 +1092,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do | |||||
end | end | ||||
def index(%{assigns: %{user: user}} = conn, _params) do | def index(%{assigns: %{user: user}} = conn, _params) do | ||||
token = | |||||
conn | |||||
|> get_session(:oauth_token) | |||||
token = get_session(conn, :oauth_token) | |||||
if user && token do | if user && token do | ||||
mastodon_emoji = mastodonized_emoji() | mastodon_emoji = mastodonized_emoji() | ||||
@@ -1122,7 +1120,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do | |||||
auto_play_gif: false, | auto_play_gif: false, | ||||
display_sensitive_media: false, | display_sensitive_media: false, | ||||
reduce_motion: false, | reduce_motion: false, | ||||
max_toot_chars: limit | |||||
max_toot_chars: limit, | |||||
mascot: "/images/pleroma-fox-tan-smol.png" | |||||
}, | }, | ||||
rights: %{ | rights: %{ | ||||
delete_others_notice: present?(user.info.is_moderator), | 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}) | |> render("index.html", %{initial_state: initial_state, flavour: flavour}) | ||||
else | else | ||||
conn | conn | ||||
|> put_session(:return_to, conn.request_path) | |||||
|> redirect(to: "/web/login") | |> redirect(to: "/web/login") | ||||
end | end | ||||
end | end | ||||
@@ -1278,12 +1278,20 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do | |||||
scope: Enum.join(app.scopes, " ") | scope: Enum.join(app.scopes, " ") | ||||
) | ) | ||||
conn | |||||
|> redirect(to: path) | |||||
redirect(conn, to: path) | |||||
end | end | ||||
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 | defp get_or_make_app do | ||||
find_attrs = %{client_name: @local_mastodon_name, redirect_uris: "."} | find_attrs = %{client_name: @local_mastodon_name, redirect_uris: "."} | ||||
@@ -147,10 +147,18 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do | |||||
content = | content = | ||||
object | object | ||||
|> render_content() | |> render_content() | ||||
|> HTML.get_cached_scrubbed_html_for_object( | |||||
|> HTML.get_cached_scrubbed_html_for_activity( | |||||
User.html_filter_policy(opts[:for]), | User.html_filter_policy(opts[:for]), | ||||
activity, | 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)) | 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), | muted: CommonAPI.thread_muted?(user, activity) || User.mutes?(opts[:for], user), | ||||
pinned: pinned?(activity, user), | pinned: pinned?(activity, user), | ||||
sensitive: sensitive, | sensitive: sensitive, | ||||
spoiler_text: object["summary"] || "", | |||||
spoiler_text: summary, | |||||
visibility: get_visibility(object), | visibility: get_visibility(object), | ||||
media_attachments: attachments, | media_attachments: attachments, | ||||
mentions: mentions, | mentions: mentions, | ||||
@@ -12,7 +12,7 @@ defmodule Pleroma.Web.Metadata.Utils do | |||||
# html content comes from DB already encoded, decode first and scrub after | # html content comes from DB already encoded, decode first and scrub after | ||||
|> HtmlEntities.decode() | |> HtmlEntities.decode() | ||||
|> String.replace(~r/<br\s?\/?>/, " ") | |> 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.demojify() | ||||
|> Formatter.truncate() | |> Formatter.truncate() | ||||
end | end | ||||
@@ -140,8 +140,12 @@ defmodule Pleroma.Web.Router do | |||||
scope "/api/pleroma/admin", Pleroma.Web.AdminAPI do | scope "/api/pleroma/admin", Pleroma.Web.AdminAPI do | ||||
pipe_through([:admin_api, :oauth_write]) | 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", AdminAPIController, :list_users) | ||||
get("/users/:nickname", AdminAPIController, :user_show) | get("/users/:nickname", AdminAPIController, :user_show) | ||||
delete("/user", AdminAPIController, :user_delete) | delete("/user", AdminAPIController, :user_delete) | ||||
patch("/users/:nickname/toggle_activation", AdminAPIController, :user_toggle_activation) | patch("/users/:nickname/toggle_activation", AdminAPIController, :user_toggle_activation) | ||||
post("/user", AdminAPIController, :user_create) | post("/user", AdminAPIController, :user_create) | ||||
@@ -254,10 +254,10 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do | |||||
html = | html = | ||||
content | content | ||||
|> HTML.get_cached_scrubbed_html_for_object( | |||||
|> HTML.get_cached_scrubbed_html_for_activity( | |||||
User.html_filter_policy(opts[:for]), | User.html_filter_policy(opts[:for]), | ||||
activity, | activity, | ||||
__MODULE__ | |||||
"twitterapi:content" | |||||
) | ) | ||||
|> Formatter.emojify(object["emoji"]) | |> Formatter.emojify(object["emoji"]) | ||||
@@ -265,7 +265,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do | |||||
if content do | if content do | ||||
content | content | ||||
|> String.replace(~r/<br\s?\/?>/, "\n") | |> 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 | else | ||||
"" | "" | ||||
end | end | ||||
@@ -41,7 +41,7 @@ defmodule Pleroma.Mixfile do | |||||
def application do | def application do | ||||
[ | [ | ||||
mod: {Pleroma.Application, []}, | mod: {Pleroma.Application, []}, | ||||
extra_applications: [:logger, :runtime_tools, :comeonin], | |||||
extra_applications: [:logger, :runtime_tools, :comeonin, :quack], | |||||
included_applications: [:ex_syslogger] | included_applications: [:ex_syslogger] | ||||
] | ] | ||||
end | end | ||||
@@ -93,8 +93,9 @@ defmodule Pleroma.Mixfile do | |||||
{:timex, "~> 3.5"}, | {:timex, "~> 3.5"}, | ||||
{:auto_linker, | {:auto_linker, | ||||
git: "https://git.pleroma.social/pleroma/auto_linker.git", | 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 | 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"}, | "base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm"}, | ||||
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "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"}, | "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"}, | "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"}, | ||||
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "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"}, | "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"}, | "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"}, | ||||
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :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"}, | "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 | 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 | def push_subscription_factory do | ||||
%Pleroma.Web.Push.Subscription{ | %Pleroma.Web.Push.Subscription{ | ||||
user: build(:user), | user: build(:user), | ||||
@@ -635,16 +635,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do | |||||
end | end | ||||
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 | describe "fetching an object" do | ||||
test "it fetches an object" do | test "it fetches an object" do | ||||
{:ok, object} = | {:ok, object} = | ||||
@@ -1,10 +1,34 @@ | |||||
defmodule Pleroma.Web.ActivityPub.UtilsTest do | defmodule Pleroma.Web.ActivityPub.UtilsTest do | ||||
use Pleroma.DataCase | 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.ActivityPub.Utils | ||||
alias Pleroma.Web.CommonAPI | alias Pleroma.Web.CommonAPI | ||||
import Pleroma.Factory | 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 | describe "determine_explicit_mentions()" do | ||||
test "works with an object that has mentions" do | test "works with an object that has mentions" do | ||||
object = %{ | object = %{ | ||||
@@ -74,6 +74,52 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do | |||||
end | end | ||||
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 | describe "PUT /api/pleroma/admin/users/tag" do | ||||
setup do | setup do | ||||
admin = insert(:user, info: %{is_admin: true}) | admin = insert(:user, info: %{is_admin: true}) | ||||
@@ -153,4 +153,40 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do | |||||
assert conversation_id == object.id | assert conversation_id == object.id | ||||
end | end | ||||
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 | end |
@@ -143,6 +143,55 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do | |||||
assert Activity.get_by_id(id) | assert Activity.get_by_id(id) | ||||
end | 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 | test "posting a status with OGP link preview", %{conn: conn} do | ||||
Pleroma.Config.put([:rich_media, :enabled], true) | Pleroma.Config.put([:rich_media, :enabled], true) | ||||
user = insert(:user) | user = insert(:user) | ||||
@@ -2307,4 +2356,71 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do | |||||
assert Map.has_key?(emoji, "visible_in_picker") | assert Map.has_key?(emoji, "visible_in_picker") | ||||
end | end | ||||
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 | end |