Merge branch 'develop' of https://git.pleroma.social/pleroma/pleroma into develop

This commit is contained in:
sadposter 2019-07-31 14:23:03 +01:00
commit 3cc6cb266b
48 changed files with 1027 additions and 393 deletions

View file

@ -6,10 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased] ## [Unreleased]
### Changed ### Changed
- **Breaking:** Configuration: A setting to explicitly disable the mailer was added, defaulting to true, if you are using a mailer add `config :pleroma, Pleroma.Emails.Mailer, enabled: true` to your config - **Breaking:** Configuration: A setting to explicitly disable the mailer was added, defaulting to true, if you are using a mailer add `config :pleroma, Pleroma.Emails.Mailer, enabled: true` to your config
- **Breaking:** Configuration: `/media/` is now removed when `base_url` is configured, append `/media/` to your `base_url` config to keep the old behaviour if desired
- Configuration: OpenGraph and TwitterCard providers enabled by default - Configuration: OpenGraph and TwitterCard providers enabled by default
- Configuration: Filter.AnonymizeFilename added ability to retain file extension with custom text - Configuration: Filter.AnonymizeFilename added ability to retain file extension with custom text
- Federation: Return 403 errors when trying to request pages from a user's follower/following collections if they have `hide_followers`/`hide_follows` set - Federation: Return 403 errors when trying to request pages from a user's follower/following collections if they have `hide_followers`/`hide_follows` set
- NodeInfo: Return `skipThreadContainment` in `metadata` for the `skip_thread_containment` option - NodeInfo: Return `skipThreadContainment` in `metadata` for the `skip_thread_containment` option
- NodeInfo: Return `mailerEnabled` in `metadata`
- Mastodon API: Unsubscribe followers when they unfollow a user - Mastodon API: Unsubscribe followers when they unfollow a user
- AdminAPI: Add "godmode" while fetching user statuses (i.e. admin can see private statuses) - AdminAPI: Add "godmode" while fetching user statuses (i.e. admin can see private statuses)
@ -26,6 +28,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Rich Media: Parser failing when no TTL can be found by image TTL setters - Rich Media: Parser failing when no TTL can be found by image TTL setters
- Rich Media: The crawled URL is now spliced into the rich media data. - Rich Media: The crawled URL is now spliced into the rich media data.
- ActivityPub S2S: sharedInbox usage has been mostly aligned with the rules in the AP specification. - ActivityPub S2S: sharedInbox usage has been mostly aligned with the rules in the AP specification.
- ActivityPub S2S: remote user deletions now work the same as local user deletions.
- Not being able to access the Mastodon FE login page on private instances
### Added ### Added
- MRF: Support for priming the mediaproxy cache (`Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy`) - MRF: Support for priming the mediaproxy cache (`Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy`)
@ -49,6 +53,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Admin API: Return avatar and display name when querying users - Admin API: Return avatar and display name when querying users
- Admin API: Allow querying user by ID - Admin API: Allow querying user by ID
- Admin API: Added support for `tuples`. - Admin API: Added support for `tuples`.
- Admin API: Added endpoints to run mix tasks pleroma.config migrate_to_db & pleroma.config migrate_from_db
- Added synchronization of following/followers counters for external users - Added synchronization of following/followers counters for external users
- Configuration: `enabled` option for `Pleroma.Emails.Mailer`, defaulting to `false`. - Configuration: `enabled` option for `Pleroma.Emails.Mailer`, defaulting to `false`.
- Configuration: Pleroma.Plugs.RateLimiter `bucket_name`, `params` options. - Configuration: Pleroma.Plugs.RateLimiter `bucket_name`, `params` options.
@ -57,6 +62,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- ActivityPub: Add an internal service actor for fetching ActivityPub objects. - ActivityPub: Add an internal service actor for fetching ActivityPub objects.
- ActivityPub: Optional signing of ActivityPub object fetches. - ActivityPub: Optional signing of ActivityPub object fetches.
- Admin API: Endpoint for fetching latest user's statuses - Admin API: Endpoint for fetching latest user's statuses
- Pleroma API: Add `/api/v1/pleroma/accounts/confirmation_resend?email=<email>` for resending account confirmation.
### Changed ### Changed
- Configuration: Filter.AnonymizeFilename added ability to retain file extension with custom text - Configuration: Filter.AnonymizeFilename added ability to retain file extension with custom text

View file

@ -534,7 +534,8 @@
relation_id_action: {60_000, 2}, relation_id_action: {60_000, 2},
statuses_actions: {10_000, 15}, statuses_actions: {10_000, 15},
status_id_action: {60_000, 3}, status_id_action: {60_000, 3},
password_reset: {1_800_000, 5} password_reset: {1_800_000, 5},
account_confirmation_resend: {8_640_000, 5}
# Import environment specific config. This must remain at the bottom # Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above. # of this file so it overrides the configuration defined above.

View file

@ -575,6 +575,29 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
- 404 Not Found `"Not found"` - 404 Not Found `"Not found"`
- On success: 200 OK `{}` - On success: 200 OK `{}`
## `/api/pleroma/admin/config/migrate_to_db`
### Run mix task pleroma.config migrate_to_db
Copy settings on key `:pleroma` to DB.
- Method `GET`
- Params: none
- Response:
```json
{}
```
## `/api/pleroma/admin/config/migrate_from_db`
### Run mix task pleroma.config migrate_from_db
Copy all settings from DB to `config/prod.exported_from_db.secret.exs` with deletion from DB.
- Method `GET`
- Params: none
- Response:
```json
{}
```
## `/api/pleroma/admin/config` ## `/api/pleroma/admin/config`
### List config settings ### List config settings
List config settings only works with `:pleroma => :instance => :dynamic_configuration` setting to `true`. List config settings only works with `:pleroma => :instance => :dynamic_configuration` setting to `true`.

View file

@ -245,6 +245,14 @@ See [Admin-API](Admin-API.md)
- PATCH `/api/v1/pleroma/accounts/update_banner`: Set/clear user banner image - PATCH `/api/v1/pleroma/accounts/update_banner`: Set/clear user banner image
- PATCH `/api/v1/pleroma/accounts/update_background`: Set/clear user background image - PATCH `/api/v1/pleroma/accounts/update_background`: Set/clear user background image
## `/api/v1/pleroma/accounts/confirmation_resend`
### Resend confirmation email
* Method `POST`
* Params:
* `email`: email of that needs to be verified
* Authentication: not required
* Response: 204 No Content
## `/api/v1/pleroma/mascot` ## `/api/v1/pleroma/mascot`
### Gets user mascot image ### Gets user mascot image
* Method `GET` * Method `GET`

View file

@ -15,7 +15,7 @@ defmodule Mix.Tasks.Pleroma.Config do
mix pleroma.config migrate_to_db mix pleroma.config migrate_to_db
## Transfers config from DB to file. ## Transfers config from DB to file `config/env.exported_from_db.secret.exs`
mix pleroma.config migrate_from_db ENV mix pleroma.config migrate_from_db ENV
""" """

View file

@ -8,6 +8,7 @@ defmodule Mix.Tasks.Pleroma.Database do
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.User alias Pleroma.User
require Logger require Logger
require Pleroma.Constants
import Mix.Pleroma import Mix.Pleroma
use Mix.Task use Mix.Task
@ -99,10 +100,15 @@ def run(["prune_objects" | args]) do
NaiveDateTime.utc_now() NaiveDateTime.utc_now()
|> NaiveDateTime.add(-(deadline * 86_400)) |> NaiveDateTime.add(-(deadline * 86_400))
public = "https://www.w3.org/ns/activitystreams#Public"
from(o in Object, from(o in Object,
where: fragment("?->'to' \\? ? OR ?->'cc' \\? ?", o.data, ^public, o.data, ^public), where:
fragment(
"?->'to' \\? ? OR ?->'cc' \\? ?",
o.data,
^Pleroma.Constants.as_public(),
o.data,
^Pleroma.Constants.as_public()
),
where: o.inserted_at < ^time_deadline, where: o.inserted_at < ^time_deadline,
where: where:
fragment("split_part(?->>'actor', '/', 3) != ?", o.data, ^Pleroma.Web.Endpoint.host()) fragment("split_part(?->>'actor', '/', 3) != ?", o.data, ^Pleroma.Web.Endpoint.host())

View file

@ -9,6 +9,8 @@ defmodule Pleroma.Activity.Search do
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.ActivityPub.Visibility
require Pleroma.Constants
import Ecto.Query import Ecto.Query
def search(user, search_query, options \\ []) do def search(user, search_query, options \\ []) do
@ -39,7 +41,7 @@ def maybe_restrict_author(query, _), do: query
defp restrict_public(q) do defp restrict_public(q) do
from([a, o] in q, from([a, o] in q,
where: fragment("?->>'type' = 'Create'", a.data), where: fragment("?->>'type' = 'Create'", a.data),
where: "https://www.w3.org/ns/activitystreams#Public" in a.recipients where: ^Pleroma.Constants.as_public() in a.recipients
) )
end end

9
lib/pleroma/constants.ex Normal file
View file

@ -0,0 +1,9 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Constants do
use Const
const(as_public, do: "https://www.w3.org/ns/activitystreams#Public")
end

View file

@ -11,6 +11,7 @@ defmodule Pleroma.HTTP.Connection do
connect_timeout: 10_000, connect_timeout: 10_000,
recv_timeout: 20_000, recv_timeout: 20_000,
follow_redirect: true, follow_redirect: true,
force_redirect: true,
pool: :federation pool: :federation
] ]
@adapter Application.get_env(:tesla, :adapter) @adapter Application.get_env(:tesla, :adapter)

View file

@ -15,7 +15,7 @@ def key_id_to_actor_id(key_id) do
|> Map.put(:fragment, nil) |> Map.put(:fragment, nil)
uri = uri =
if String.ends_with?(uri.path, "/publickey") do if not is_nil(uri.path) and String.ends_with?(uri.path, "/publickey") do
Map.put(uri, :path, String.replace(uri.path, "/publickey", "")) Map.put(uri, :path, String.replace(uri.path, "/publickey", ""))
else else
uri uri

View file

@ -471,7 +471,7 @@ def set_cache(%User{} = user) do
end end
def update_and_set_cache(changeset) do def update_and_set_cache(changeset) do
with {:ok, user} <- Repo.update(changeset) do with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
set_cache(user) set_cache(user)
else else
e -> e e -> e

View file

@ -23,6 +23,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
import Pleroma.Web.ActivityPub.Visibility import Pleroma.Web.ActivityPub.Visibility
require Logger require Logger
require Pleroma.Constants
# For Announce activities, we filter the recipients based on following status for any actors # For Announce activities, we filter the recipients based on following status for any actors
# that match actual users. See issue #164 for more information about why this is necessary. # that match actual users. See issue #164 for more information about why this is necessary.
@ -207,8 +208,6 @@ def stream_out_participations(%Object{data: %{"context" => context}}, user) do
def stream_out_participations(_, _), do: :noop def stream_out_participations(_, _), do: :noop
def stream_out(activity) do def stream_out(activity) do
public = "https://www.w3.org/ns/activitystreams#Public"
if activity.data["type"] in ["Create", "Announce", "Delete"] do if activity.data["type"] in ["Create", "Announce", "Delete"] do
object = Object.normalize(activity) object = Object.normalize(activity)
# Do not stream out poll replies # Do not stream out poll replies
@ -216,7 +215,7 @@ def stream_out(activity) do
Pleroma.Web.Streamer.stream("user", activity) Pleroma.Web.Streamer.stream("user", activity)
Pleroma.Web.Streamer.stream("list", activity) Pleroma.Web.Streamer.stream("list", activity)
if Enum.member?(activity.data["to"], public) do if get_visibility(activity) == "public" do
Pleroma.Web.Streamer.stream("public", activity) Pleroma.Web.Streamer.stream("public", activity)
if activity.local do if activity.local do
@ -238,13 +237,8 @@ def stream_out(activity) do
end end
end end
else else
# TODO: Write test, replace with visibility test if get_visibility(activity) == "direct",
if !Enum.member?(activity.data["cc"] || [], public) && do: Pleroma.Web.Streamer.stream("direct", activity)
!Enum.member?(
activity.data["to"],
User.get_cached_by_ap_id(activity.data["actor"]).follower_address
),
do: Pleroma.Web.Streamer.stream("direct", activity)
end end
end end
end end
@ -514,7 +508,7 @@ def flag(
end end
defp fetch_activities_for_context_query(context, opts) do defp fetch_activities_for_context_query(context, opts) do
public = ["https://www.w3.org/ns/activitystreams#Public"] public = [Pleroma.Constants.as_public()]
recipients = recipients =
if opts["user"], do: [opts["user"].ap_id | opts["user"].following] ++ public, else: public if opts["user"], do: [opts["user"].ap_id | opts["user"].following] ++ public, else: public
@ -555,7 +549,7 @@ def fetch_latest_activity_id_for_context(context, opts \\ %{}) do
end end
def fetch_public_activities(opts \\ %{}) do def fetch_public_activities(opts \\ %{}) do
q = fetch_activities_query(["https://www.w3.org/ns/activitystreams#Public"], opts) q = fetch_activities_query([Pleroma.Constants.as_public()], opts)
q q
|> restrict_unlisted() |> restrict_unlisted()
@ -646,10 +640,9 @@ defp user_activities_recipients(%{"godmode" => true}) do
defp user_activities_recipients(%{"reading_user" => reading_user}) do defp user_activities_recipients(%{"reading_user" => reading_user}) do
if reading_user do if reading_user do
["https://www.w3.org/ns/activitystreams#Public"] ++ [Pleroma.Constants.as_public()] ++ [reading_user.ap_id | reading_user.following]
[reading_user.ap_id | reading_user.following]
else else
["https://www.w3.org/ns/activitystreams#Public"] [Pleroma.Constants.as_public()]
end end
end end
@ -834,7 +827,7 @@ defp restrict_unlisted(query) do
fragment( fragment(
"not (coalesce(?->'cc', '{}'::jsonb) \\?| ?)", "not (coalesce(?->'cc', '{}'::jsonb) \\?| ?)",
activity.data, activity.data,
^["https://www.w3.org/ns/activitystreams#Public"] ^[Pleroma.Constants.as_public()]
) )
) )
end end
@ -971,7 +964,7 @@ def fetch_activities_bounded_query(query, recipients, recipients_with_public) do
where: where:
fragment("? && ?", activity.recipients, ^recipients) or fragment("? && ?", activity.recipients, ^recipients) or
(fragment("? && ?", activity.recipients, ^recipients_with_public) and (fragment("? && ?", activity.recipients, ^recipients_with_public) and
"https://www.w3.org/ns/activitystreams#Public" in activity.recipients) ^Pleroma.Constants.as_public() in activity.recipients)
) )
end end

View file

@ -4,6 +4,9 @@
defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do
alias Pleroma.User alias Pleroma.User
require Pleroma.Constants
@moduledoc "Block messages with too much mentions (configurable)" @moduledoc "Block messages with too much mentions (configurable)"
@behaviour Pleroma.Web.ActivityPub.MRF @behaviour Pleroma.Web.ActivityPub.MRF
@ -19,12 +22,12 @@ defp delist_message(message, threshold) when threshold > 0 do
when follower_collection? and recipients > threshold -> when follower_collection? and recipients > threshold ->
message message
|> Map.put("to", [follower_collection]) |> Map.put("to", [follower_collection])
|> Map.put("cc", ["https://www.w3.org/ns/activitystreams#Public"]) |> Map.put("cc", [Pleroma.Constants.as_public()])
{:public, recipients} when recipients > threshold -> {:public, recipients} when recipients > threshold ->
message message
|> Map.put("to", []) |> Map.put("to", [])
|> Map.put("cc", ["https://www.w3.org/ns/activitystreams#Public"]) |> Map.put("cc", [Pleroma.Constants.as_public()])
_ -> _ ->
message message
@ -51,10 +54,10 @@ defp get_recipient_count(message) do
recipients = (message["to"] || []) ++ (message["cc"] || []) recipients = (message["to"] || []) ++ (message["cc"] || [])
follower_collection = User.get_cached_by_ap_id(message["actor"]).follower_address follower_collection = User.get_cached_by_ap_id(message["actor"]).follower_address
if Enum.member?(recipients, "https://www.w3.org/ns/activitystreams#Public") do if Enum.member?(recipients, Pleroma.Constants.as_public()) do
recipients = recipients =
recipients recipients
|> List.delete("https://www.w3.org/ns/activitystreams#Public") |> List.delete(Pleroma.Constants.as_public())
|> List.delete(follower_collection) |> List.delete(follower_collection)
{:public, length(recipients)} {:public, length(recipients)}

View file

@ -3,6 +3,8 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
require Pleroma.Constants
@moduledoc "Reject or Word-Replace messages with a keyword or regex" @moduledoc "Reject or Word-Replace messages with a keyword or regex"
@behaviour Pleroma.Web.ActivityPub.MRF @behaviour Pleroma.Web.ActivityPub.MRF
@ -31,12 +33,12 @@ defp check_reject(%{"object" => %{"content" => content, "summary" => summary}} =
defp check_ftl_removal( defp check_ftl_removal(
%{"to" => to, "object" => %{"content" => content, "summary" => summary}} = message %{"to" => to, "object" => %{"content" => content, "summary" => summary}} = message
) do ) do
if "https://www.w3.org/ns/activitystreams#Public" in to and if Pleroma.Constants.as_public() in to and
Enum.any?(Pleroma.Config.get([:mrf_keyword, :federated_timeline_removal]), fn pattern -> Enum.any?(Pleroma.Config.get([:mrf_keyword, :federated_timeline_removal]), fn pattern ->
string_matches?(content, pattern) or string_matches?(summary, pattern) string_matches?(content, pattern) or string_matches?(summary, pattern)
end) do end) do
to = List.delete(to, "https://www.w3.org/ns/activitystreams#Public") to = List.delete(to, Pleroma.Constants.as_public())
cc = ["https://www.w3.org/ns/activitystreams#Public" | message["cc"] || []] cc = [Pleroma.Constants.as_public() | message["cc"] || []]
message = message =
message message

View file

@ -10,7 +10,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.RejectNonPublic do
@behaviour Pleroma.Web.ActivityPub.MRF @behaviour Pleroma.Web.ActivityPub.MRF
@public "https://www.w3.org/ns/activitystreams#Public" require Pleroma.Constants
@impl true @impl true
def filter(%{"type" => "Create"} = object) do def filter(%{"type" => "Create"} = object) do
@ -19,8 +19,8 @@ def filter(%{"type" => "Create"} = object) do
# Determine visibility # Determine visibility
visibility = visibility =
cond do cond do
@public in object["to"] -> "public" Pleroma.Constants.as_public() in object["to"] -> "public"
@public in object["cc"] -> "unlisted" Pleroma.Constants.as_public() in object["cc"] -> "unlisted"
user.follower_address in object["to"] -> "followers" user.follower_address in object["to"] -> "followers"
true -> "direct" true -> "direct"
end end

View file

@ -8,6 +8,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
@moduledoc "Filter activities depending on their origin instance" @moduledoc "Filter activities depending on their origin instance"
@behaviour MRF @behaviour MRF
require Pleroma.Constants
defp check_accept(%{host: actor_host} = _actor_info, object) do defp check_accept(%{host: actor_host} = _actor_info, object) do
accepts = accepts =
Pleroma.Config.get([:mrf_simple, :accept]) Pleroma.Config.get([:mrf_simple, :accept])
@ -89,14 +91,10 @@ defp check_ftl_removal(%{host: actor_host} = _actor_info, object) do
object = object =
with true <- MRF.subdomain_match?(timeline_removal, actor_host), with true <- MRF.subdomain_match?(timeline_removal, actor_host),
user <- User.get_cached_by_ap_id(object["actor"]), user <- User.get_cached_by_ap_id(object["actor"]),
true <- "https://www.w3.org/ns/activitystreams#Public" in object["to"] do true <- Pleroma.Constants.as_public() in object["to"] do
to = to = List.delete(object["to"], Pleroma.Constants.as_public()) ++ [user.follower_address]
List.delete(object["to"], "https://www.w3.org/ns/activitystreams#Public") ++
[user.follower_address]
cc = cc = List.delete(object["cc"], user.follower_address) ++ [Pleroma.Constants.as_public()]
List.delete(object["cc"], user.follower_address) ++
["https://www.w3.org/ns/activitystreams#Public"]
object object
|> Map.put("to", to) |> Map.put("to", to)

View file

@ -19,7 +19,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do
- `mrf_tag:disable-any-subscription`: Reject any follow requests - `mrf_tag:disable-any-subscription`: Reject any follow requests
""" """
@public "https://www.w3.org/ns/activitystreams#Public" require Pleroma.Constants
defp get_tags(%User{tags: tags}) when is_list(tags), do: tags defp get_tags(%User{tags: tags}) when is_list(tags), do: tags
defp get_tags(_), do: [] defp get_tags(_), do: []
@ -70,9 +70,9 @@ defp process_tag(
) do ) do
user = User.get_cached_by_ap_id(actor) user = User.get_cached_by_ap_id(actor)
if Enum.member?(to, @public) do if Enum.member?(to, Pleroma.Constants.as_public()) do
to = List.delete(to, @public) ++ [user.follower_address] to = List.delete(to, Pleroma.Constants.as_public()) ++ [user.follower_address]
cc = List.delete(cc, user.follower_address) ++ [@public] cc = List.delete(cc, user.follower_address) ++ [Pleroma.Constants.as_public()]
object = object =
object object
@ -103,9 +103,10 @@ defp process_tag(
) do ) do
user = User.get_cached_by_ap_id(actor) user = User.get_cached_by_ap_id(actor)
if Enum.member?(to, @public) or Enum.member?(cc, @public) do if Enum.member?(to, Pleroma.Constants.as_public()) or
to = List.delete(to, @public) ++ [user.follower_address] Enum.member?(cc, Pleroma.Constants.as_public()) do
cc = List.delete(cc, @public) to = List.delete(to, Pleroma.Constants.as_public()) ++ [user.follower_address]
cc = List.delete(cc, Pleroma.Constants.as_public())
object = object =
object object

View file

@ -11,6 +11,8 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
alias Pleroma.Web.ActivityPub.Relay alias Pleroma.Web.ActivityPub.Relay
alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.ActivityPub.Transmogrifier
require Pleroma.Constants
import Pleroma.Web.ActivityPub.Visibility import Pleroma.Web.ActivityPub.Visibility
@behaviour Pleroma.Web.Federator.Publisher @behaviour Pleroma.Web.Federator.Publisher
@ -117,8 +119,6 @@ defp get_cc_ap_ids(ap_id, recipients) do
|> Enum.map(& &1.ap_id) |> Enum.map(& &1.ap_id)
end end
@as_public "https://www.w3.org/ns/activitystreams#Public"
defp maybe_use_sharedinbox(%User{info: %{source_data: data}}), defp maybe_use_sharedinbox(%User{info: %{source_data: data}}),
do: (is_map(data["endpoints"]) && Map.get(data["endpoints"], "sharedInbox")) || data["inbox"] do: (is_map(data["endpoints"]) && Map.get(data["endpoints"], "sharedInbox")) || data["inbox"]
@ -145,7 +145,7 @@ def determine_inbox(
type == "Delete" -> type == "Delete" ->
maybe_use_sharedinbox(user) maybe_use_sharedinbox(user)
@as_public in to || @as_public in cc -> Pleroma.Constants.as_public() in to || Pleroma.Constants.as_public() in cc ->
maybe_use_sharedinbox(user) maybe_use_sharedinbox(user)
length(to) + length(cc) > 1 -> length(to) + length(cc) > 1 ->

View file

@ -19,6 +19,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
import Ecto.Query import Ecto.Query
require Logger require Logger
require Pleroma.Constants
@doc """ @doc """
Modifies an incoming AP object (mastodon format) to our internal format. Modifies an incoming AP object (mastodon format) to our internal format.
@ -102,8 +103,7 @@ def fix_explicit_addressing(object) do
follower_collection = User.get_cached_by_ap_id(Containment.get_actor(object)).follower_address follower_collection = User.get_cached_by_ap_id(Containment.get_actor(object)).follower_address
explicit_mentions = explicit_mentions = explicit_mentions ++ [Pleroma.Constants.as_public(), follower_collection]
explicit_mentions ++ ["https://www.w3.org/ns/activitystreams#Public", follower_collection]
fix_explicit_addressing(object, explicit_mentions, follower_collection) fix_explicit_addressing(object, explicit_mentions, follower_collection)
end end
@ -115,11 +115,11 @@ def fix_implicit_addressing(%{"to" => to, "cc" => cc} = object, followers_collec
if followers_collection not in recipients do if followers_collection not in recipients do
cond do cond do
"https://www.w3.org/ns/activitystreams#Public" in cc -> Pleroma.Constants.as_public() in cc ->
to = to ++ [followers_collection] to = to ++ [followers_collection]
Map.put(object, "to", to) Map.put(object, "to", to)
"https://www.w3.org/ns/activitystreams#Public" in to -> Pleroma.Constants.as_public() in to ->
cc = cc ++ [followers_collection] cc = cc ++ [followers_collection]
Map.put(object, "cc", cc) Map.put(object, "cc", cc)
@ -480,8 +480,7 @@ def handle_incoming(
{:ok, %User{} = follower} <- User.get_or_fetch_by_ap_id(follower), {:ok, %User{} = follower} <- User.get_or_fetch_by_ap_id(follower),
{:ok, activity} <- ActivityPub.follow(follower, followed, id, false) do {:ok, activity} <- ActivityPub.follow(follower, followed, id, false) do
with deny_follow_blocked <- Pleroma.Config.get([:user, :deny_follow_blocked]), with deny_follow_blocked <- Pleroma.Config.get([:user, :deny_follow_blocked]),
{_, false} <- {_, false} <- {:user_blocked, User.blocks?(followed, follower) && deny_follow_blocked},
{:user_blocked, User.blocks?(followed, follower) && deny_follow_blocked},
{_, false} <- {:user_locked, User.locked?(followed)}, {_, false} <- {:user_locked, User.locked?(followed)},
{_, {:ok, follower}} <- {:follow, User.follow(follower, followed)}, {_, {:ok, follower}} <- {:follow, User.follow(follower, followed)},
{_, {:ok, _}} <- {_, {:ok, _}} <-
@ -656,20 +655,7 @@ def handle_incoming(
nil -> nil ->
case User.get_cached_by_ap_id(object_id) do case User.get_cached_by_ap_id(object_id) do
%User{ap_id: ^actor} = user -> %User{ap_id: ^actor} = user ->
{:ok, followers} = User.get_followers(user) User.delete(user)
Enum.each(followers, fn follower ->
User.unfollow(follower, user)
end)
{:ok, friends} = User.get_friends(user)
Enum.each(friends, fn followed ->
User.unfollow(user, followed)
end)
User.invalidate_cache(user)
Repo.delete(user)
nil -> nil ->
:error :error

View file

@ -18,6 +18,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
import Ecto.Query import Ecto.Query
require Logger require Logger
require Pleroma.Constants
@supported_object_types ["Article", "Note", "Video", "Page", "Question", "Answer"] @supported_object_types ["Article", "Note", "Video", "Page", "Question", "Answer"]
@supported_report_states ~w(open closed resolved) @supported_report_states ~w(open closed resolved)
@ -418,7 +419,7 @@ def make_follow_data(
"type" => "Follow", "type" => "Follow",
"actor" => follower_id, "actor" => follower_id,
"to" => [followed_id], "to" => [followed_id],
"cc" => ["https://www.w3.org/ns/activitystreams#Public"], "cc" => [Pleroma.Constants.as_public()],
"object" => followed_id, "object" => followed_id,
"state" => "pending" "state" => "pending"
} }
@ -510,7 +511,7 @@ def make_announce_data(
"actor" => ap_id, "actor" => ap_id,
"object" => id, "object" => id,
"to" => [user.follower_address, object.data["actor"]], "to" => [user.follower_address, object.data["actor"]],
"cc" => ["https://www.w3.org/ns/activitystreams#Public"], "cc" => [Pleroma.Constants.as_public()],
"context" => object.data["context"] "context" => object.data["context"]
} }
@ -530,7 +531,7 @@ def make_unannounce_data(
"actor" => ap_id, "actor" => ap_id,
"object" => activity.data, "object" => activity.data,
"to" => [user.follower_address, activity.data["actor"]], "to" => [user.follower_address, activity.data["actor"]],
"cc" => ["https://www.w3.org/ns/activitystreams#Public"], "cc" => [Pleroma.Constants.as_public()],
"context" => context "context" => context
} }
@ -547,7 +548,7 @@ def make_unlike_data(
"actor" => ap_id, "actor" => ap_id,
"object" => activity.data, "object" => activity.data,
"to" => [user.follower_address, activity.data["actor"]], "to" => [user.follower_address, activity.data["actor"]],
"cc" => ["https://www.w3.org/ns/activitystreams#Public"], "cc" => [Pleroma.Constants.as_public()],
"context" => context "context" => context
} }
@ -556,7 +557,7 @@ def make_unlike_data(
def add_announce_to_object( def add_announce_to_object(
%Activity{ %Activity{
data: %{"actor" => actor, "cc" => ["https://www.w3.org/ns/activitystreams#Public"]} data: %{"actor" => actor, "cc" => [Pleroma.Constants.as_public()]}
}, },
object object
) do ) do
@ -765,7 +766,7 @@ defp get_updated_targets(
) do ) do
cc = Map.get(data, "cc", []) cc = Map.get(data, "cc", [])
follower_address = User.get_cached_by_ap_id(data["actor"]).follower_address follower_address = User.get_cached_by_ap_id(data["actor"]).follower_address
public = "https://www.w3.org/ns/activitystreams#Public" public = Pleroma.Constants.as_public()
case visibility do case visibility do
"public" -> "public" ->

View file

@ -8,14 +8,14 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.User alias Pleroma.User
@public "https://www.w3.org/ns/activitystreams#Public" require Pleroma.Constants
@spec is_public?(Object.t() | Activity.t() | map()) :: boolean() @spec is_public?(Object.t() | Activity.t() | map()) :: boolean()
def is_public?(%Object{data: %{"type" => "Tombstone"}}), do: false def is_public?(%Object{data: %{"type" => "Tombstone"}}), do: false
def is_public?(%Object{data: data}), do: is_public?(data) def is_public?(%Object{data: data}), do: is_public?(data)
def is_public?(%Activity{data: data}), do: is_public?(data) def is_public?(%Activity{data: data}), do: is_public?(data)
def is_public?(%{"directMessage" => true}), do: false def is_public?(%{"directMessage" => true}), do: false
def is_public?(data), do: @public in (data["to"] ++ (data["cc"] || [])) def is_public?(data), do: Pleroma.Constants.as_public() in (data["to"] ++ (data["cc"] || []))
def is_private?(activity) do def is_private?(activity) do
with false <- is_public?(activity), with false <- is_public?(activity),
@ -73,10 +73,10 @@ def get_visibility(object) do
cc = object.data["cc"] || [] cc = object.data["cc"] || []
cond do cond do
@public in to -> Pleroma.Constants.as_public() in to ->
"public" "public"
@public in cc -> Pleroma.Constants.as_public() in cc ->
"unlisted" "unlisted"
# this should use the sql for the object's activity # this should use the sql for the object's activity

View file

@ -379,6 +379,16 @@ def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
end end
end end
def migrate_to_db(conn, _params) do
Mix.Tasks.Pleroma.Config.run(["migrate_to_db"])
json(conn, %{})
end
def migrate_from_db(conn, _params) do
Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "true"])
json(conn, %{})
end
def config_show(conn, _params) do def config_show(conn, _params) do
configs = Pleroma.Repo.all(Config) configs = Pleroma.Repo.all(Config)

View file

@ -21,8 +21,7 @@ def get_user(plug), do: implementation().get_user(plug)
def create_from_registration(plug, registration), def create_from_registration(plug, registration),
do: implementation().create_from_registration(plug, registration) do: implementation().create_from_registration(plug, registration)
@callback get_registration(Plug.Conn.t()) :: @callback get_registration(Plug.Conn.t()) :: {:ok, Registration.t()} | {:error, any()}
{:ok, Registration.t()} | {:error, any()}
def get_registration(plug), do: implementation().get_registration(plug) def get_registration(plug), do: implementation().get_registration(plug)
@callback handle_error(Plug.Conn.t(), any()) :: any() @callback handle_error(Plug.Conn.t(), any()) :: any()

View file

@ -300,8 +300,7 @@ def pin(id_or_ap_id, %{ap_id: user_ap_id} = user) do
} }
} = activity <- get_by_id_or_ap_id(id_or_ap_id), } = activity <- get_by_id_or_ap_id(id_or_ap_id),
true <- Visibility.is_public?(activity), true <- Visibility.is_public?(activity),
%{valid?: true} = info_changeset <- %{valid?: true} = info_changeset <- User.Info.add_pinnned_activity(user.info, activity),
User.Info.add_pinnned_activity(user.info, activity),
changeset <- changeset <-
Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset), Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset),
{:ok, _user} <- User.update_and_set_cache(changeset) do {:ok, _user} <- User.update_and_set_cache(changeset) do

View file

@ -19,6 +19,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
alias Pleroma.Web.MediaProxy alias Pleroma.Web.MediaProxy
require Logger require Logger
require Pleroma.Constants
# 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
@ -66,7 +67,7 @@ def attachments_from_ids_descs(ids, descs_str) do
@spec get_to_and_cc(User.t(), list(String.t()), Activity.t() | nil, String.t()) :: @spec get_to_and_cc(User.t(), list(String.t()), Activity.t() | nil, String.t()) ::
{list(String.t()), list(String.t())} {list(String.t()), list(String.t())}
def get_to_and_cc(user, mentioned_users, inReplyTo, "public") do def get_to_and_cc(user, mentioned_users, inReplyTo, "public") do
to = ["https://www.w3.org/ns/activitystreams#Public" | mentioned_users] to = [Pleroma.Constants.as_public() | mentioned_users]
cc = [user.follower_address] cc = [user.follower_address]
if inReplyTo do if inReplyTo do
@ -78,7 +79,7 @@ def get_to_and_cc(user, mentioned_users, inReplyTo, "public") do
def get_to_and_cc(user, mentioned_users, inReplyTo, "unlisted") do def get_to_and_cc(user, mentioned_users, inReplyTo, "unlisted") do
to = [user.follower_address | mentioned_users] to = [user.follower_address | mentioned_users]
cc = ["https://www.w3.org/ns/activitystreams#Public"] cc = [Pleroma.Constants.as_public()]
if inReplyTo do if inReplyTo do
{Enum.uniq([inReplyTo.data["actor"] | to]), cc} {Enum.uniq([inReplyTo.data["actor"] | to]), cc}

View file

@ -0,0 +1,77 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Fallback.RedirectController do
use Pleroma.Web, :controller
require Logger
alias Pleroma.User
alias Pleroma.Web.Metadata
def api_not_implemented(conn, _params) do
conn
|> put_status(404)
|> json(%{error: "Not implemented"})
end
def redirector(conn, _params, code \\ 200)
# redirect to admin section
# /pleroma/admin -> /pleroma/admin/
#
def redirector(conn, %{"path" => ["pleroma", "admin"]} = _, _code) do
redirect(conn, to: "/pleroma/admin/")
end
def redirector(conn, _params, code) do
conn
|> put_resp_content_type("text/html")
|> send_file(code, index_file_path())
end
def redirector_with_meta(conn, %{"maybe_nickname_or_id" => maybe_nickname_or_id} = params) do
with %User{} = user <- User.get_cached_by_nickname_or_id(maybe_nickname_or_id) do
redirector_with_meta(conn, %{user: user})
else
nil ->
redirector(conn, params)
end
end
def redirector_with_meta(conn, params) do
{:ok, index_content} = File.read(index_file_path())
tags =
try do
Metadata.build_tags(params)
rescue
e ->
Logger.error(
"Metadata rendering for #{conn.request_path} failed.\n" <>
Exception.format(:error, e, __STACKTRACE__)
)
""
end
response = String.replace(index_content, "<!--server-generated-meta-->", tags)
conn
|> put_resp_content_type("text/html")
|> send_resp(200, response)
end
def index_file_path do
Pleroma.Plugs.InstanceStatic.file_path("index.html")
end
def registration_page(conn, params) do
redirector(conn, params)
end
def empty(conn, _params) do
conn
|> put_status(204)
|> text("")
end
end

View file

@ -4,6 +4,9 @@
defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
import Pleroma.Web.ControllerHelper, only: [json_response: 3]
alias Ecto.Changeset alias Ecto.Changeset
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Bookmark alias Pleroma.Bookmark
@ -46,6 +49,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
import Ecto.Query import Ecto.Query
require Logger require Logger
require Pleroma.Constants
@rate_limited_relations_actions ~w(follow unfollow)a @rate_limited_relations_actions ~w(follow unfollow)a
@ -74,6 +78,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
plug(RateLimiter, :app_account_creation when action == :account_register) plug(RateLimiter, :app_account_creation when action == :account_register)
plug(RateLimiter, :search when action in [:search, :search2, :account_search]) plug(RateLimiter, :search when action in [:search, :search2, :account_search])
plug(RateLimiter, :password_reset when action == :password_reset) plug(RateLimiter, :password_reset when action == :password_reset)
plug(RateLimiter, :account_confirmation_resend when action == :account_confirmation_resend)
@local_mastodon_name "Mastodon-Local" @local_mastodon_name "Mastodon-Local"
@ -1220,10 +1225,9 @@ def user_favourites(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params
recipients = recipients =
if for_user do if for_user do
["https://www.w3.org/ns/activitystreams#Public"] ++ [Pleroma.Constants.as_public()] ++ [for_user.ap_id | for_user.following]
[for_user.ap_id | for_user.following]
else else
["https://www.w3.org/ns/activitystreams#Public"] [Pleroma.Constants.as_public()]
end end
activities = activities =
@ -1839,6 +1843,16 @@ def password_reset(conn, params) do
end end
end end
def account_confirmation_resend(conn, params) do
nickname_or_email = params["email"] || params["nickname"]
with %User{} = user <- User.get_by_nickname_or_email(nickname_or_email),
{:ok, _} <- User.try_send_confirmation_email(user) do
conn
|> json_response(:no_content, "")
end
end
def try_render(conn, target, params) def try_render(conn, target, params)
when is_binary(target) do when is_binary(target) do
case render(conn, target, params) do case render(conn, target, params) do

View file

@ -165,6 +165,7 @@ def raw_nodeinfo do
}, },
accountActivationRequired: Config.get([:instance, :account_activation_required], false), accountActivationRequired: Config.get([:instance, :account_activation_required], false),
invitesEnabled: Config.get([:instance, :invites_enabled], false), invitesEnabled: Config.get([:instance, :invites_enabled], false),
mailerEnabled: Config.get([Pleroma.Emails.Mailer, :enabled], false),
features: features, features: features,
restrictedNicknames: Config.get([Pleroma.User, :restricted_nicknames]), restrictedNicknames: Config.get([Pleroma.User, :restricted_nicknames]),
skipThreadContainment: Config.get([:instance, :skip_thread_containment], false) skipThreadContainment: Config.get([:instance, :skip_thread_containment], false)

View file

@ -365,8 +365,7 @@ def registration_details(%Plug.Conn{} = conn, %{"authorization" => auth_attrs})
def register(%Plug.Conn{} = conn, %{"authorization" => _, "op" => "connect"} = params) do def register(%Plug.Conn{} = conn, %{"authorization" => _, "op" => "connect"} = params) do
with registration_id when not is_nil(registration_id) <- get_session_registration_id(conn), with registration_id when not is_nil(registration_id) <- get_session_registration_id(conn),
%Registration{} = registration <- Repo.get(Registration, registration_id), %Registration{} = registration <- Repo.get(Registration, registration_id),
{_, {:ok, auth}} <- {_, {:ok, auth}} <- {:create_authorization, do_create_authorization(conn, params)},
{:create_authorization, do_create_authorization(conn, params)},
%User{} = user <- Repo.preload(auth, :user).user, %User{} = user <- Repo.preload(auth, :user).user,
{:ok, _updated_registration} <- Registration.bind_to_user(registration, user) do {:ok, _updated_registration} <- Registration.bind_to_user(registration, user) do
conn conn

View file

@ -44,8 +44,7 @@ def get_by_refresh_token(%App{id: app_id} = _app, token) do
|> Repo.find_resource() |> Repo.find_resource()
end end
@spec exchange_token(App.t(), Authorization.t()) :: @spec exchange_token(App.t(), Authorization.t()) :: {:ok, Token.t()} | {:error, Changeset.t()}
{:ok, Token.t()} | {:error, Changeset.t()}
def exchange_token(app, auth) do def exchange_token(app, auth) do
with {:ok, auth} <- Authorization.use_token(auth), with {:ok, auth} <- Authorization.use_token(auth),
true <- auth.app_id == app.id do true <- auth.app_id == app.id do

View file

@ -9,6 +9,7 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
alias Pleroma.Web.OStatus.UserRepresenter alias Pleroma.Web.OStatus.UserRepresenter
require Logger require Logger
require Pleroma.Constants
defp get_href(id) do defp get_href(id) do
with %Object{data: %{"external_url" => external_url}} <- Object.get_cached_by_ap_id(id) do with %Object{data: %{"external_url" => external_url}} <- Object.get_cached_by_ap_id(id) do
@ -34,7 +35,7 @@ defp get_mentions(to) do
Enum.map(to, fn id -> Enum.map(to, fn id ->
cond do cond do
# Special handling for the AP/Ostatus public collections # Special handling for the AP/Ostatus public collections
"https://www.w3.org/ns/activitystreams#Public" == id -> Pleroma.Constants.as_public() == id ->
{:link, {:link,
[ [
rel: "mentioned", rel: "mentioned",

View file

@ -4,6 +4,7 @@
defmodule Pleroma.Web.OStatus.NoteHandler do defmodule Pleroma.Web.OStatus.NoteHandler do
require Logger require Logger
require Pleroma.Constants
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Object alias Pleroma.Object
@ -49,7 +50,7 @@ def get_people_mentions(entry) do
def get_collection_mentions(entry) do def get_collection_mentions(entry) do
transmogrify = fn transmogrify = fn
"http://activityschema.org/collection/public" -> "http://activityschema.org/collection/public" ->
"https://www.w3.org/ns/activitystreams#Public" Pleroma.Constants.as_public()
group -> group ->
group group
@ -126,7 +127,7 @@ def handle_note(entry, doc \\ nil, options \\ []) do
to <- make_to_list(actor, mentions), to <- make_to_list(actor, mentions),
date <- XML.string_from_xpath("//published", entry), date <- XML.string_from_xpath("//published", entry),
unlisted <- XML.string_from_xpath("//mastodon:scope", entry) == "unlisted", unlisted <- XML.string_from_xpath("//mastodon:scope", entry) == "unlisted",
cc <- if(unlisted, do: ["https://www.w3.org/ns/activitystreams#Public"], else: []), cc <- if(unlisted, do: [Pleroma.Constants.as_public()], else: []),
note <- note <-
CommonAPI.Utils.make_note_data( CommonAPI.Utils.make_note_data(
actor.ap_id, actor.ap_id,

View file

@ -5,6 +5,7 @@
defmodule Pleroma.Web.OStatus.OStatusController do defmodule Pleroma.Web.OStatus.OStatusController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
alias Fallback.RedirectController
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.User alias Pleroma.User
@ -12,42 +13,44 @@ defmodule Pleroma.Web.OStatus.OStatusController do
alias Pleroma.Web.ActivityPub.ActivityPubController alias Pleroma.Web.ActivityPub.ActivityPubController
alias Pleroma.Web.ActivityPub.ObjectView alias Pleroma.Web.ActivityPub.ObjectView
alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.Endpoint
alias Pleroma.Web.Federator alias Pleroma.Web.Federator
alias Pleroma.Web.Metadata.PlayerView
alias Pleroma.Web.OStatus alias Pleroma.Web.OStatus
alias Pleroma.Web.OStatus.ActivityRepresenter alias Pleroma.Web.OStatus.ActivityRepresenter
alias Pleroma.Web.OStatus.FeedRepresenter alias Pleroma.Web.OStatus.FeedRepresenter
alias Pleroma.Web.Router
alias Pleroma.Web.XML alias Pleroma.Web.XML
plug(Pleroma.Web.FederatingPlug when action in [:salmon_incoming]) plug(Pleroma.Web.FederatingPlug when action in [:salmon_incoming])
plug(
Pleroma.Plugs.SetFormatPlug
when action in [:feed_redirect, :object, :activity, :notice]
)
action_fallback(:errors) action_fallback(:errors)
def feed_redirect(%{assigns: %{format: "html"}} = conn, %{"nickname" => nickname}) do
with {_, %User{} = user} <-
{:fetch_user, User.get_cached_by_nickname_or_id(nickname)} do
RedirectController.redirector_with_meta(conn, %{user: user})
end
end
def feed_redirect(%{assigns: %{format: format}} = conn, _params)
when format in ["json", "activity+json"] do
ActivityPubController.call(conn, :user)
end
def feed_redirect(conn, %{"nickname" => nickname}) do def feed_redirect(conn, %{"nickname" => nickname}) do
case get_format(conn) do with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do
"html" -> redirect(conn, external: OStatus.feed_path(user))
with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
Fallback.RedirectController.redirector_with_meta(conn, %{user: user})
else
nil -> {:error, :not_found}
end
"activity+json" ->
ActivityPubController.call(conn, :user)
"json" ->
ActivityPubController.call(conn, :user)
_ ->
with %User{} = user <- User.get_cached_by_nickname(nickname) do
redirect(conn, external: OStatus.feed_path(user))
else
nil -> {:error, :not_found}
end
end end
end end
def feed(conn, %{"nickname" => nickname} = params) do def feed(conn, %{"nickname" => nickname} = params) do
with %User{} = user <- User.get_cached_by_nickname(nickname) do with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do
query_params = query_params =
Map.take(params, ["max_id"]) Map.take(params, ["max_id"])
|> Map.merge(%{"whole_db" => true, "actor_id" => user.ap_id}) |> Map.merge(%{"whole_db" => true, "actor_id" => user.ap_id})
@ -65,8 +68,6 @@ def feed(conn, %{"nickname" => nickname} = params) do
conn conn
|> put_resp_content_type("application/atom+xml") |> put_resp_content_type("application/atom+xml")
|> send_resp(200, response) |> send_resp(200, response)
else
nil -> {:error, :not_found}
end end
end end
@ -97,93 +98,82 @@ def salmon_incoming(conn, _) do
|> send_resp(200, "") |> send_resp(200, "")
end end
def object(conn, %{"uuid" => uuid}) do def object(%{assigns: %{format: format}} = conn, %{"uuid" => _uuid})
if get_format(conn) in ["activity+json", "json"] do when format in ["json", "activity+json"] do
ActivityPubController.call(conn, :object) ActivityPubController.call(conn, :object)
else end
with id <- o_status_url(conn, :object, uuid),
{_, %Activity{} = activity} <-
{:activity, Activity.get_create_by_object_ap_id_with_object(id)},
{_, true} <- {:public?, Visibility.is_public?(activity)},
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
case get_format(conn) do
"html" -> redirect(conn, to: "/notice/#{activity.id}")
_ -> represent_activity(conn, nil, activity, user)
end
else
{:public?, false} ->
{:error, :not_found}
{:activity, nil} -> def object(%{assigns: %{format: format}} = conn, %{"uuid" => uuid}) do
{:error, :not_found} with id <- o_status_url(conn, :object, uuid),
{_, %Activity{} = activity} <-
e -> {:activity, Activity.get_create_by_object_ap_id_with_object(id)},
e {_, true} <- {:public?, Visibility.is_public?(activity)},
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
case format do
"html" -> redirect(conn, to: "/notice/#{activity.id}")
_ -> represent_activity(conn, nil, activity, user)
end end
else
reason when reason in [{:public?, false}, {:activity, nil}] ->
{:error, :not_found}
e ->
e
end end
end end
def activity(conn, %{"uuid" => uuid}) do def activity(%{assigns: %{format: format}} = conn, %{"uuid" => _uuid})
if get_format(conn) in ["activity+json", "json"] do when format in ["json", "activity+json"] do
ActivityPubController.call(conn, :activity) ActivityPubController.call(conn, :activity)
else end
with id <- o_status_url(conn, :activity, uuid),
{_, %Activity{} = activity} <- {:activity, Activity.normalize(id)},
{_, true} <- {:public?, Visibility.is_public?(activity)},
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
case format = get_format(conn) do
"html" -> redirect(conn, to: "/notice/#{activity.id}")
_ -> represent_activity(conn, format, activity, user)
end
else
{:public?, false} ->
{:error, :not_found}
{:activity, nil} -> def activity(%{assigns: %{format: format}} = conn, %{"uuid" => uuid}) do
{:error, :not_found} with id <- o_status_url(conn, :activity, uuid),
{_, %Activity{} = activity} <- {:activity, Activity.normalize(id)},
e -> {_, true} <- {:public?, Visibility.is_public?(activity)},
e %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
case format do
"html" -> redirect(conn, to: "/notice/#{activity.id}")
_ -> represent_activity(conn, format, activity, user)
end end
else
reason when reason in [{:public?, false}, {:activity, nil}] ->
{:error, :not_found}
e ->
e
end end
end end
def notice(conn, %{"id" => id}) do def notice(%{assigns: %{format: format}} = conn, %{"id" => id}) do
with {_, %Activity{} = activity} <- {:activity, Activity.get_by_id_with_object(id)}, with {_, %Activity{} = activity} <- {:activity, Activity.get_by_id_with_object(id)},
{_, true} <- {:public?, Visibility.is_public?(activity)}, {_, true} <- {:public?, Visibility.is_public?(activity)},
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
case format = get_format(conn) do cond do
"html" -> format == "html" && activity.data["type"] == "Create" ->
if activity.data["type"] == "Create" do %Object{} = object = Object.normalize(activity)
%Object{} = object = Object.normalize(activity)
Fallback.RedirectController.redirector_with_meta(conn, %{ RedirectController.redirector_with_meta(
conn,
%{
activity_id: activity.id, activity_id: activity.id,
object: object, object: object,
url: url: Router.Helpers.o_status_url(Endpoint, :notice, activity.id),
Pleroma.Web.Router.Helpers.o_status_url(
Pleroma.Web.Endpoint,
:notice,
activity.id
),
user: user user: user
}) }
else )
Fallback.RedirectController.redirector(conn, nil)
end
_ -> format == "html" ->
RedirectController.redirector(conn, nil)
true ->
represent_activity(conn, format, activity, user) represent_activity(conn, format, activity, user)
end end
else else
{:public?, false} -> reason when reason in [{:public?, false}, {:activity, nil}] ->
conn conn
|> put_status(404) |> put_status(404)
|> Fallback.RedirectController.redirector(nil, 404) |> RedirectController.redirector(nil, 404)
{:activity, nil} ->
conn
|> Fallback.RedirectController.redirector(nil, 404)
e -> e ->
e e
@ -204,13 +194,13 @@ def notice_player(conn, %{"id" => id}) do
"content-security-policy", "content-security-policy",
"default-src 'none';style-src 'self' 'unsafe-inline';img-src 'self' data: https:; media-src 'self' https:;" "default-src 'none';style-src 'self' 'unsafe-inline';img-src 'self' data: https:; media-src 'self' https:;"
) )
|> put_view(Pleroma.Web.Metadata.PlayerView) |> put_view(PlayerView)
|> render("player.html", url) |> render("player.html", url)
else else
_error -> _error ->
conn conn
|> put_status(404) |> put_status(404)
|> Fallback.RedirectController.redirector(nil, 404) |> RedirectController.redirector(nil, 404)
end end
end end
@ -248,6 +238,8 @@ def errors(conn, {:error, :not_found}) do
render_error(conn, :not_found, "Not found") render_error(conn, :not_found, "Not found")
end end
def errors(conn, {:fetch_user, nil}), do: errors(conn, {:error, :not_found})
def errors(conn, _) do def errors(conn, _) do
render_error(conn, :internal_server_error, "Something went wrong") render_error(conn, :internal_server_error, "Something went wrong")
end end

View file

@ -19,8 +19,7 @@ defp is_aws_signed_url(nil), do: nil
defp is_aws_signed_url(image) when is_binary(image) do defp is_aws_signed_url(image) when is_binary(image) do
%URI{host: host, query: query} = URI.parse(image) %URI{host: host, query: query} = URI.parse(image)
if String.contains?(host, "amazonaws.com") and if String.contains?(host, "amazonaws.com") and String.contains?(query, "X-Amz-Expires") do
String.contains?(query, "X-Amz-Expires") do
image image
else else
nil nil

View file

@ -196,6 +196,8 @@ defmodule Pleroma.Web.Router do
get("/config", AdminAPIController, :config_show) get("/config", AdminAPIController, :config_show)
post("/config", AdminAPIController, :config_update) post("/config", AdminAPIController, :config_update)
get("/config/migrate_to_db", AdminAPIController, :migrate_to_db)
get("/config/migrate_from_db", AdminAPIController, :migrate_from_db)
end end
scope "/", Pleroma.Web.TwitterAPI do scope "/", Pleroma.Web.TwitterAPI do
@ -412,6 +414,12 @@ defmodule Pleroma.Web.Router do
get("/accounts/search", SearchController, :account_search) get("/accounts/search", SearchController, :account_search)
post(
"/pleroma/accounts/confirmation_resend",
MastodonAPIController,
:account_confirmation_resend
)
scope [] do scope [] do
pipe_through(:oauth_read_or_public) pipe_through(:oauth_read_or_public)
@ -692,7 +700,7 @@ defmodule Pleroma.Web.Router do
post("/auth/password", MastodonAPIController, :password_reset) post("/auth/password", MastodonAPIController, :password_reset)
scope [] do scope [] do
pipe_through(:oauth_read_or_public) pipe_through(:oauth_read)
get("/web/*path", MastodonAPIController, :index) get("/web/*path", MastodonAPIController, :index)
end end
end end
@ -729,68 +737,3 @@ defmodule Pleroma.Web.Router do
options("/*path", RedirectController, :empty) options("/*path", RedirectController, :empty)
end end
end end
defmodule Fallback.RedirectController do
use Pleroma.Web, :controller
require Logger
alias Pleroma.User
alias Pleroma.Web.Metadata
def api_not_implemented(conn, _params) do
conn
|> put_status(404)
|> json(%{error: "Not implemented"})
end
def redirector(conn, _params, code \\ 200) do
conn
|> put_resp_content_type("text/html")
|> send_file(code, index_file_path())
end
def redirector_with_meta(conn, %{"maybe_nickname_or_id" => maybe_nickname_or_id} = params) do
with %User{} = user <- User.get_cached_by_nickname_or_id(maybe_nickname_or_id) do
redirector_with_meta(conn, %{user: user})
else
nil ->
redirector(conn, params)
end
end
def redirector_with_meta(conn, params) do
{:ok, index_content} = File.read(index_file_path())
tags =
try do
Metadata.build_tags(params)
rescue
e ->
Logger.error(
"Metadata rendering for #{conn.request_path} failed.\n" <>
Exception.format(:error, e, __STACKTRACE__)
)
""
end
response = String.replace(index_content, "<!--server-generated-meta-->", tags)
conn
|> put_resp_content_type("text/html")
|> send_resp(200, response)
end
def index_file_path do
Pleroma.Plugs.InstanceStatic.file_path("index.html")
end
def registration_page(conn, params) do
redirector(conn, params)
end
def empty(conn, _params) do
conn
|> put_status(204)
|> text("")
end
end

View file

@ -15,7 +15,6 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
alias Pleroma.Plugs.AuthenticationPlug alias Pleroma.Plugs.AuthenticationPlug
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web alias Pleroma.Web
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI
alias Pleroma.Web.WebFinger alias Pleroma.Web.WebFinger
@ -100,8 +99,7 @@ def do_remote_follow(conn, %{
with %User{} = user <- User.get_cached_by_nickname(username), with %User{} = user <- User.get_cached_by_nickname(username),
true <- AuthenticationPlug.checkpw(password, user.password_hash), true <- AuthenticationPlug.checkpw(password, user.password_hash),
%User{} = _followed <- User.get_cached_by_id(id), %User{} = _followed <- User.get_cached_by_id(id),
{:ok, follower} <- User.follow(user, followee), {:ok, _follower, _followee, _activity} <- CommonAPI.follow(user, followee) do
{:ok, _activity} <- ActivityPub.follow(follower, followee) do
conn conn
|> render("followed.html", %{error: false}) |> render("followed.html", %{error: false})
else else
@ -122,8 +120,7 @@ def do_remote_follow(conn, %{
def do_remote_follow(%{assigns: %{user: user}} = conn, %{"user" => %{"id" => id}}) do def do_remote_follow(%{assigns: %{user: user}} = conn, %{"user" => %{"id" => id}}) do
with %User{} = followee <- User.get_cached_by_id(id), with %User{} = followee <- User.get_cached_by_id(id),
{:ok, follower} <- User.follow(user, followee), {:ok, _follower, _followee, _activity} <- CommonAPI.follow(user, followee) do
{:ok, _activity} <- ActivityPub.follow(follower, followee) do
conn conn
|> render("followed.html", %{error: false}) |> render("followed.html", %{error: false})
else else

View file

@ -15,6 +15,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
import Ecto.Query import Ecto.Query
require Pleroma.Constants
def create_status(%User{} = user, %{"status" => _} = data) do def create_status(%User{} = user, %{"status" => _} = data) do
CommonAPI.post(user, data) CommonAPI.post(user, data)
end end
@ -286,7 +288,7 @@ def search(_user, %{"q" => query} = params) do
from( from(
[a, o] in Activity.with_preloaded_object(Activity), [a, o] in Activity.with_preloaded_object(Activity),
where: fragment("?->>'type' = 'Create'", a.data), where: fragment("?->>'type' = 'Create'", a.data),
where: "https://www.w3.org/ns/activitystreams#Public" in a.recipients, where: ^Pleroma.Constants.as_public() in a.recipients,
where: where:
fragment( fragment(
"to_tsvector('english', ?->>'content') @@ plainto_tsquery('english', ?)", "to_tsvector('english', ?->>'content') @@ plainto_tsquery('english', ?)",

View file

@ -19,6 +19,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do
import Ecto.Query import Ecto.Query
require Logger require Logger
require Pleroma.Constants
defp query_context_ids([]), do: [] defp query_context_ids([]), do: []
@ -91,7 +92,7 @@ defp get_user(ap_id, opts) do
String.ends_with?(ap_id, "/followers") -> String.ends_with?(ap_id, "/followers") ->
nil nil
ap_id == "https://www.w3.org/ns/activitystreams#Public" -> ap_id == Pleroma.Constants.as_public() ->
nil nil
user = User.get_cached_by_ap_id(ap_id) -> user = User.get_cached_by_ap_id(ap_id) ->

View file

@ -10,6 +10,8 @@ defmodule Pleroma.Web.TwitterAPI.NotificationView do
alias Pleroma.Web.TwitterAPI.ActivityView alias Pleroma.Web.TwitterAPI.ActivityView
alias Pleroma.Web.TwitterAPI.UserView alias Pleroma.Web.TwitterAPI.UserView
require Pleroma.Constants
defp get_user(ap_id, opts) do defp get_user(ap_id, opts) do
cond do cond do
user = opts[:users][ap_id] -> user = opts[:users][ap_id] ->
@ -18,7 +20,7 @@ defp get_user(ap_id, opts) do
String.ends_with?(ap_id, "/followers") -> String.ends_with?(ap_id, "/followers") ->
nil nil
ap_id == "https://www.w3.org/ns/activitystreams#Public" -> ap_id == Pleroma.Constants.as_public() ->
nil nil
true -> true ->

View file

@ -150,6 +150,7 @@ defp deps do
{:benchee, "~> 1.0"}, {:benchee, "~> 1.0"},
{:esshd, "~> 0.1.0", runtime: Application.get_env(:esshd, :enabled, false)}, {:esshd, "~> 0.1.0", runtime: Application.get_env(:esshd, :enabled, false)},
{:ex_rated, "~> 1.3"}, {:ex_rated, "~> 1.3"},
{:ex_const, "~> 0.2"},
{:plug_static_index_html, "~> 1.0.0"}, {:plug_static_index_html, "~> 1.0.0"},
{:excoveralls, "~> 0.11.1", only: :test}, {:excoveralls, "~> 0.11.1", only: :test},
{:mox, "~> 0.5", only: :test} {:mox, "~> 0.5", only: :test}

View file

@ -27,6 +27,7 @@
"ex2ms": {:hex, :ex2ms, "1.5.0", "19e27f9212be9a96093fed8cdfbef0a2b56c21237196d26760f11dfcfae58e97", [:mix], [], "hexpm"}, "ex2ms": {:hex, :ex2ms, "1.5.0", "19e27f9212be9a96093fed8cdfbef0a2b56c21237196d26760f11dfcfae58e97", [:mix], [], "hexpm"},
"ex_aws": {:hex, :ex_aws, "2.1.0", "b92651527d6c09c479f9013caa9c7331f19cba38a650590d82ebf2c6c16a1d8a", [:mix], [{:configparser_ex, "~> 2.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "1.6.3 or 1.6.5 or 1.7.1 or 1.8.6 or ~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8", [hex: :jsx, repo: "hexpm", optional: true]}, {:poison, ">= 1.2.0", [hex: :poison, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:xml_builder, "~> 0.1.0", [hex: :xml_builder, repo: "hexpm", optional: true]}], "hexpm"}, "ex_aws": {:hex, :ex_aws, "2.1.0", "b92651527d6c09c479f9013caa9c7331f19cba38a650590d82ebf2c6c16a1d8a", [:mix], [{:configparser_ex, "~> 2.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "1.6.3 or 1.6.5 or 1.7.1 or 1.8.6 or ~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8", [hex: :jsx, repo: "hexpm", optional: true]}, {:poison, ">= 1.2.0", [hex: :poison, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:xml_builder, "~> 0.1.0", [hex: :xml_builder, repo: "hexpm", optional: true]}], "hexpm"},
"ex_aws_s3": {:hex, :ex_aws_s3, "2.0.1", "9e09366e77f25d3d88c5393824e613344631be8db0d1839faca49686e99b6704", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm"}, "ex_aws_s3": {:hex, :ex_aws_s3, "2.0.1", "9e09366e77f25d3d88c5393824e613344631be8db0d1839faca49686e99b6704", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm"},
"ex_const": {:hex, :ex_const, "0.2.4", "d06e540c9d834865b012a17407761455efa71d0ce91e5831e86881b9c9d82448", [:mix], [], "hexpm"},
"ex_doc": {:hex, :ex_doc, "0.20.2", "1bd0dfb0304bade58beb77f20f21ee3558cc3c753743ae0ddbb0fd7ba2912331", [:mix], [{:earmark, "~> 1.3", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.10", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"}, "ex_doc": {:hex, :ex_doc, "0.20.2", "1bd0dfb0304bade58beb77f20f21ee3558cc3c753743ae0ddbb0fd7ba2912331", [:mix], [{:earmark, "~> 1.3", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.10", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"},
"ex_machina": {:hex, :ex_machina, "2.3.0", "92a5ad0a8b10ea6314b876a99c8c9e3f25f4dde71a2a835845b136b9adaf199a", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm"}, "ex_machina": {:hex, :ex_machina, "2.3.0", "92a5ad0a8b10ea6314b876a99c8c9e3f25f4dde71a2a835845b136b9adaf199a", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm"},
"ex_rated": {:hex, :ex_rated, "1.3.3", "30ecbdabe91f7eaa9d37fa4e81c85ba420f371babeb9d1910adbcd79ec798d27", [:mix], [{:ex2ms, "~> 1.5", [hex: :ex2ms, repo: "hexpm", optional: false]}], "hexpm"}, "ex_rated": {:hex, :ex_rated, "1.3.3", "30ecbdabe91f7eaa9d37fa4e81c85ba420f371babeb9d1910adbcd79ec798d27", [:mix], [{:ex2ms, "~> 1.5", [hex: :ex2ms, repo: "hexpm", optional: false]}], "hexpm"},

View file

@ -564,6 +564,64 @@ test "replying to a deleted post without tagging does not generate a notificatio
assert Enum.empty?(Notification.for_user(user)) assert Enum.empty?(Notification.for_user(user))
end end
test "notifications are deleted if a local user is deleted" do
user = insert(:user)
other_user = insert(:user)
{:ok, _activity} =
CommonAPI.post(user, %{"status" => "hi @#{other_user.nickname}", "visibility" => "direct"})
refute Enum.empty?(Notification.for_user(other_user))
User.delete(user)
assert Enum.empty?(Notification.for_user(other_user))
end
test "notifications are deleted if a remote user is deleted" do
remote_user = insert(:user)
local_user = insert(:user)
dm_message = %{
"@context" => "https://www.w3.org/ns/activitystreams",
"type" => "Create",
"actor" => remote_user.ap_id,
"id" => remote_user.ap_id <> "/activities/test",
"to" => [local_user.ap_id],
"cc" => [],
"object" => %{
"type" => "Note",
"content" => "Hello!",
"tag" => [
%{
"type" => "Mention",
"href" => local_user.ap_id,
"name" => "@#{local_user.nickname}"
}
],
"to" => [local_user.ap_id],
"cc" => [],
"attributedTo" => remote_user.ap_id
}
}
{:ok, _dm_activity} = Transmogrifier.handle_incoming(dm_message)
refute Enum.empty?(Notification.for_user(local_user))
delete_user_message = %{
"@context" => "https://www.w3.org/ns/activitystreams",
"id" => remote_user.ap_id <> "/activities/delete",
"actor" => remote_user.ap_id,
"type" => "Delete",
"object" => remote_user.ap_id
}
{:ok, _delete_activity} = Transmogrifier.handle_incoming(delete_user_message)
assert Enum.empty?(Notification.for_user(local_user))
end
end end
describe "for_user" do describe "for_user" do

View file

@ -1369,4 +1369,28 @@ test "user with internal-prefixed nickname returns true" do
assert User.is_internal_user?(user) assert User.is_internal_user?(user)
end end
end end
describe "update_and_set_cache/1" do
test "returns error when user is stale instead Ecto.StaleEntryError" do
user = insert(:user)
changeset = Ecto.Changeset.change(user, bio: "test")
Repo.delete(user)
assert {:error, %Ecto.Changeset{errors: [id: {"is stale", [stale: true]}], valid?: false}} =
User.update_and_set_cache(changeset)
end
test "performs update cache if user updated" do
user = insert(:user)
assert {:ok, nil} = Cachex.get(:user_cache, "ap_id:#{user.ap_id}")
changeset = Ecto.Changeset.change(user, bio: "test-bio")
assert {:ok, %User{bio: "test-bio"} = user} = User.update_and_set_cache(changeset)
assert {:ok, user} = Cachex.get(:user_cache, "ap_id:#{user.ap_id}")
assert %User{bio: "test-bio"} = User.get_cached_by_ap_id(user.ap_id)
end
end
end end

View file

@ -1916,6 +1916,43 @@ test "queues key as atom", %{conn: conn} do
end end
end end
describe "config mix tasks run" do
setup %{conn: conn} do
admin = insert(:user, info: %{is_admin: true})
temp_file = "config/test.exported_from_db.secret.exs"
on_exit(fn ->
:ok = File.rm(temp_file)
end)
dynamic = Pleroma.Config.get([:instance, :dynamic_configuration])
Pleroma.Config.put([:instance, :dynamic_configuration], true)
on_exit(fn ->
Pleroma.Config.put([:instance, :dynamic_configuration], dynamic)
end)
%{conn: assign(conn, :user, admin), admin: admin}
end
test "transfer settings to DB and to file", %{conn: conn, admin: admin} do
assert Pleroma.Repo.all(Pleroma.Web.AdminAPI.Config) == []
conn = get(conn, "/api/pleroma/admin/config/migrate_to_db")
assert json_response(conn, 200) == %{}
assert Pleroma.Repo.all(Pleroma.Web.AdminAPI.Config) > 0
conn =
build_conn()
|> assign(:user, admin)
|> get("/api/pleroma/admin/config/migrate_from_db")
assert json_response(conn, 200) == %{}
assert Pleroma.Repo.all(Pleroma.Web.AdminAPI.Config) == []
end
end
describe "GET /api/pleroma/admin/users/:nickname/statuses" do describe "GET /api/pleroma/admin/users/:nickname/statuses" do
setup do setup do
admin = insert(:user, info: %{is_admin: true}) admin = insert(:user, info: %{is_admin: true})

View file

@ -30,6 +30,10 @@ test "GET /api*path", %{conn: conn} do
|> json_response(404) == %{"error" => "Not implemented"} |> json_response(404) == %{"error" => "Not implemented"}
end end
test "GET /pleroma/admin -> /pleroma/admin/", %{conn: conn} do
assert redirected_to(get(conn, "/pleroma/admin")) =~ "/pleroma/admin/"
end
test "GET /*path", %{conn: conn} do test "GET /*path", %{conn: conn} do
assert conn assert conn
|> get("/foo") |> get("/foo")

View file

@ -3154,6 +3154,21 @@ test "redirects not logged-in users to the login page", %{conn: conn, path: path
assert redirected_to(conn) == "/web/login" assert redirected_to(conn) == "/web/login"
end end
test "redirects not logged-in users to the login page on private instances", %{
conn: conn,
path: path
} do
is_public = Pleroma.Config.get([:instance, :public])
Pleroma.Config.put([:instance, :public], false)
conn = get(conn, path)
assert conn.status == 302
assert redirected_to(conn) == "/web/login"
Pleroma.Config.put([:instance, :public], is_public)
end
test "does not redirect logged in users to the login page", %{conn: conn, path: path} do test "does not redirect logged in users to the login page", %{conn: conn, path: path} do
token = insert(:oauth_token) token = insert(:oauth_token)
@ -3923,4 +3938,45 @@ test "it returns 400 when user is not local", %{conn: conn, user: user} do
assert conn.resp_body == "" assert conn.resp_body == ""
end end
end end
describe "POST /api/v1/pleroma/accounts/confirmation_resend" do
setup do
setting = Pleroma.Config.get([:instance, :account_activation_required])
unless setting do
Pleroma.Config.put([:instance, :account_activation_required], true)
on_exit(fn -> Pleroma.Config.put([:instance, :account_activation_required], setting) end)
end
user = insert(:user)
info_change = User.Info.confirmation_changeset(user.info, need_confirmation: true)
{:ok, user} =
user
|> Changeset.change()
|> Changeset.put_embed(:info, info_change)
|> Repo.update()
assert user.info.confirmation_pending
[user: user]
end
test "resend account confirmation email", %{conn: conn, user: user} do
conn
|> assign(:user, user)
|> post("/api/v1/pleroma/accounts/confirmation_resend?email=#{user.email}")
|> json_response(:no_content)
email = Pleroma.Emails.UserEmail.account_confirmation_email(user)
notify_email = Pleroma.Config.get([:instance, :notify_email])
instance_name = Pleroma.Config.get([:instance, :name])
assert_email_sent(
from: {instance_name, notify_email},
to: {user.name, user.email},
html_body: email.html_body
)
end
end
end end

View file

@ -101,160 +101,538 @@ test "returns 404 for a missing feed", %{conn: conn} do
assert response(conn, 404) assert response(conn, 404)
end end
test "gets an object", %{conn: conn} do describe "GET object/2" do
note_activity = insert(:note_activity) test "gets an object", %{conn: conn} do
object = Object.normalize(note_activity) note_activity = insert(:note_activity)
user = User.get_cached_by_ap_id(note_activity.data["actor"]) object = Object.normalize(note_activity)
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, object.data["id"])) user = User.get_cached_by_ap_id(note_activity.data["actor"])
url = "/objects/#{uuid}" [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, object.data["id"]))
url = "/objects/#{uuid}"
conn =
conn
|> put_req_header("accept", "application/xml")
|> get(url)
expected =
ActivityRepresenter.to_simple_form(note_activity, user, true)
|> ActivityRepresenter.wrap_with_entry()
|> :xmerl.export_simple(:xmerl_xml)
|> to_string
assert response(conn, 200) == expected
end
test "redirects to /notice/id for html format", %{conn: conn} do
note_activity = insert(:note_activity)
object = Object.normalize(note_activity)
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, object.data["id"]))
url = "/objects/#{uuid}"
conn =
conn
|> put_req_header("accept", "text/html")
|> get(url)
assert redirected_to(conn) == "/notice/#{note_activity.id}"
end
test "500s when user not found", %{conn: conn} do
note_activity = insert(:note_activity)
object = Object.normalize(note_activity)
user = User.get_cached_by_ap_id(note_activity.data["actor"])
User.invalidate_cache(user)
Pleroma.Repo.delete(user)
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, object.data["id"]))
url = "/objects/#{uuid}"
conn =
conn
|> put_req_header("accept", "application/xml")
|> get(url)
assert response(conn, 500) == ~S({"error":"Something went wrong"})
end
test "404s on private objects", %{conn: conn} do
note_activity = insert(:direct_note_activity)
object = Object.normalize(note_activity)
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, object.data["id"]))
conn
|> get("/objects/#{uuid}")
|> response(404)
end
test "404s on nonexisting objects", %{conn: conn} do
conn
|> get("/objects/123")
|> response(404)
end
end
describe "GET activity/2" do
test "gets an activity in xml format", %{conn: conn} do
note_activity = insert(:note_activity)
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"]))
conn =
conn conn
|> put_req_header("accept", "application/xml") |> put_req_header("accept", "application/xml")
|> get(url) |> get("/activities/#{uuid}")
|> response(200)
end
expected = test "redirects to /notice/id for html format", %{conn: conn} do
ActivityRepresenter.to_simple_form(note_activity, user, true) note_activity = insert(:note_activity)
|> ActivityRepresenter.wrap_with_entry() [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"]))
|> :xmerl.export_simple(:xmerl_xml)
|> to_string
assert response(conn, 200) == expected conn =
conn
|> put_req_header("accept", "text/html")
|> get("/activities/#{uuid}")
assert redirected_to(conn) == "/notice/#{note_activity.id}"
end
test "505s when user not found", %{conn: conn} do
note_activity = insert(:note_activity)
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"]))
user = User.get_cached_by_ap_id(note_activity.data["actor"])
User.invalidate_cache(user)
Pleroma.Repo.delete(user)
conn =
conn
|> put_req_header("accept", "text/html")
|> get("/activities/#{uuid}")
assert response(conn, 500) == ~S({"error":"Something went wrong"})
end
test "404s on deleted objects", %{conn: conn} do
note_activity = insert(:note_activity)
object = Object.normalize(note_activity)
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, object.data["id"]))
conn
|> put_req_header("accept", "application/xml")
|> get("/objects/#{uuid}")
|> response(200)
Object.delete(object)
conn
|> put_req_header("accept", "application/xml")
|> get("/objects/#{uuid}")
|> response(404)
end
test "404s on private activities", %{conn: conn} do
note_activity = insert(:direct_note_activity)
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"]))
conn
|> get("/activities/#{uuid}")
|> response(404)
end
test "404s on nonexistent activities", %{conn: conn} do
conn
|> get("/activities/123")
|> response(404)
end
test "gets an activity in AS2 format", %{conn: conn} do
note_activity = insert(:note_activity)
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"]))
url = "/activities/#{uuid}"
conn =
conn
|> put_req_header("accept", "application/activity+json")
|> get(url)
assert json_response(conn, 200)
end
end end
test "404s on private objects", %{conn: conn} do describe "GET notice/2" do
note_activity = insert(:direct_note_activity) test "gets a notice in xml format", %{conn: conn} do
object = Object.normalize(note_activity) note_activity = insert(:note_activity)
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, object.data["id"]))
conn conn
|> get("/objects/#{uuid}") |> get("/notice/#{note_activity.id}")
|> response(404) |> response(200)
end end
test "404s on nonexisting objects", %{conn: conn} do test "gets a notice in AS2 format", %{conn: conn} do
conn note_activity = insert(:note_activity)
|> get("/objects/123")
|> response(404)
end
test "gets an activity in xml format", %{conn: conn} do
note_activity = insert(:note_activity)
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"]))
conn
|> put_req_header("accept", "application/xml")
|> get("/activities/#{uuid}")
|> response(200)
end
test "404s on deleted objects", %{conn: conn} do
note_activity = insert(:note_activity)
object = Object.normalize(note_activity)
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, object.data["id"]))
conn
|> put_req_header("accept", "application/xml")
|> get("/objects/#{uuid}")
|> response(200)
Object.delete(object)
conn
|> put_req_header("accept", "application/xml")
|> get("/objects/#{uuid}")
|> response(404)
end
test "404s on private activities", %{conn: conn} do
note_activity = insert(:direct_note_activity)
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"]))
conn
|> get("/activities/#{uuid}")
|> response(404)
end
test "404s on nonexistent activities", %{conn: conn} do
conn
|> get("/activities/123")
|> response(404)
end
test "gets a notice in xml format", %{conn: conn} do
note_activity = insert(:note_activity)
conn
|> get("/notice/#{note_activity.id}")
|> response(200)
end
test "gets a notice in AS2 format", %{conn: conn} do
note_activity = insert(:note_activity)
conn
|> put_req_header("accept", "application/activity+json")
|> get("/notice/#{note_activity.id}")
|> json_response(200)
end
test "only gets a notice in AS2 format for Create messages", %{conn: conn} do
note_activity = insert(:note_activity)
url = "/notice/#{note_activity.id}"
conn =
conn conn
|> put_req_header("accept", "application/activity+json") |> put_req_header("accept", "application/activity+json")
|> get(url) |> get("/notice/#{note_activity.id}")
|> json_response(200)
end
assert json_response(conn, 200) test "500s when actor not found", %{conn: conn} do
note_activity = insert(:note_activity)
user = User.get_cached_by_ap_id(note_activity.data["actor"])
User.invalidate_cache(user)
Pleroma.Repo.delete(user)
user = insert(:user) conn =
conn
|> get("/notice/#{note_activity.id}")
{:ok, like_activity, _} = CommonAPI.favorite(note_activity.id, user) assert response(conn, 500) == ~S({"error":"Something went wrong"})
url = "/notice/#{like_activity.id}" end
assert like_activity.data["type"] == "Like" test "only gets a notice in AS2 format for Create messages", %{conn: conn} do
note_activity = insert(:note_activity)
url = "/notice/#{note_activity.id}"
conn = conn =
build_conn() conn
|> put_req_header("accept", "application/activity+json") |> put_req_header("accept", "application/activity+json")
|> get(url) |> get(url)
assert response(conn, 404) assert json_response(conn, 200)
user = insert(:user)
{:ok, like_activity, _} = CommonAPI.favorite(note_activity.id, user)
url = "/notice/#{like_activity.id}"
assert like_activity.data["type"] == "Like"
conn =
build_conn()
|> put_req_header("accept", "application/activity+json")
|> get(url)
assert response(conn, 404)
end
test "render html for redirect for html format", %{conn: conn} do
note_activity = insert(:note_activity)
resp =
conn
|> put_req_header("accept", "text/html")
|> get("/notice/#{note_activity.id}")
|> response(200)
assert resp =~
"<meta content=\"#{Pleroma.Web.base_url()}/notice/#{note_activity.id}\" property=\"og:url\">"
user = insert(:user)
{:ok, like_activity, _} = CommonAPI.favorite(note_activity.id, user)
assert like_activity.data["type"] == "Like"
resp =
conn
|> put_req_header("accept", "text/html")
|> get("/notice/#{like_activity.id}")
|> response(200)
assert resp =~ "<!--server-generated-meta-->"
end
test "404s a private notice", %{conn: conn} do
note_activity = insert(:direct_note_activity)
url = "/notice/#{note_activity.id}"
conn =
conn
|> get(url)
assert response(conn, 404)
end
test "404s a nonexisting notice", %{conn: conn} do
url = "/notice/123"
conn =
conn
|> get(url)
assert response(conn, 404)
end
end end
test "gets an activity in AS2 format", %{conn: conn} do describe "feed_redirect" do
note_activity = insert(:note_activity) test "undefined format. it redirects to feed", %{conn: conn} do
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"])) note_activity = insert(:note_activity)
url = "/activities/#{uuid}" user = User.get_cached_by_ap_id(note_activity.data["actor"])
conn = response =
conn conn
|> put_req_header("accept", "application/activity+json") |> put_req_header("accept", "application/xml")
|> get(url) |> get("/users/#{user.nickname}")
|> response(302)
assert json_response(conn, 200) assert response ==
"<html><body>You are being <a href=\"#{Pleroma.Web.base_url()}/users/#{
user.nickname
}/feed.atom\">redirected</a>.</body></html>"
end
test "undefined format. it returns error when user not found", %{conn: conn} do
response =
conn
|> put_req_header("accept", "application/xml")
|> get("/users/jimm")
|> response(404)
assert response == ~S({"error":"Not found"})
end
test "activity+json format. it redirects on actual feed of user", %{conn: conn} do
note_activity = insert(:note_activity)
user = User.get_cached_by_ap_id(note_activity.data["actor"])
response =
conn
|> put_req_header("accept", "application/activity+json")
|> get("/users/#{user.nickname}")
|> json_response(200)
assert response["endpoints"] == %{
"oauthAuthorizationEndpoint" => "#{Pleroma.Web.base_url()}/oauth/authorize",
"oauthRegistrationEndpoint" => "#{Pleroma.Web.base_url()}/api/v1/apps",
"oauthTokenEndpoint" => "#{Pleroma.Web.base_url()}/oauth/token",
"sharedInbox" => "#{Pleroma.Web.base_url()}/inbox"
}
assert response["@context"] == [
"https://www.w3.org/ns/activitystreams",
"http://localhost:4001/schemas/litepub-0.1.jsonld",
%{"@language" => "und"}
]
assert Map.take(response, [
"followers",
"following",
"id",
"inbox",
"manuallyApprovesFollowers",
"name",
"outbox",
"preferredUsername",
"summary",
"tag",
"type",
"url"
]) == %{
"followers" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/followers",
"following" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/following",
"id" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}",
"inbox" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/inbox",
"manuallyApprovesFollowers" => false,
"name" => user.name,
"outbox" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/outbox",
"preferredUsername" => user.nickname,
"summary" => user.bio,
"tag" => [],
"type" => "Person",
"url" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}"
}
end
test "activity+json format. it returns error whe use not found", %{conn: conn} do
response =
conn
|> put_req_header("accept", "application/activity+json")
|> get("/users/jimm")
|> json_response(404)
assert response == "Not found"
end
test "json format. it redirects on actual feed of user", %{conn: conn} do
note_activity = insert(:note_activity)
user = User.get_cached_by_ap_id(note_activity.data["actor"])
response =
conn
|> put_req_header("accept", "application/json")
|> get("/users/#{user.nickname}")
|> json_response(200)
assert response["endpoints"] == %{
"oauthAuthorizationEndpoint" => "#{Pleroma.Web.base_url()}/oauth/authorize",
"oauthRegistrationEndpoint" => "#{Pleroma.Web.base_url()}/api/v1/apps",
"oauthTokenEndpoint" => "#{Pleroma.Web.base_url()}/oauth/token",
"sharedInbox" => "#{Pleroma.Web.base_url()}/inbox"
}
assert response["@context"] == [
"https://www.w3.org/ns/activitystreams",
"http://localhost:4001/schemas/litepub-0.1.jsonld",
%{"@language" => "und"}
]
assert Map.take(response, [
"followers",
"following",
"id",
"inbox",
"manuallyApprovesFollowers",
"name",
"outbox",
"preferredUsername",
"summary",
"tag",
"type",
"url"
]) == %{
"followers" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/followers",
"following" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/following",
"id" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}",
"inbox" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/inbox",
"manuallyApprovesFollowers" => false,
"name" => user.name,
"outbox" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/outbox",
"preferredUsername" => user.nickname,
"summary" => user.bio,
"tag" => [],
"type" => "Person",
"url" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}"
}
end
test "json format. it returns error whe use not found", %{conn: conn} do
response =
conn
|> put_req_header("accept", "application/json")
|> get("/users/jimm")
|> json_response(404)
assert response == "Not found"
end
test "html format. it redirects on actual feed of user", %{conn: conn} do
note_activity = insert(:note_activity)
user = User.get_cached_by_ap_id(note_activity.data["actor"])
response =
conn
|> get("/users/#{user.nickname}")
|> response(200)
assert response ==
Fallback.RedirectController.redirector_with_meta(
conn,
%{user: user}
).resp_body
end
test "html format. it returns error when user not found", %{conn: conn} do
response =
conn
|> get("/users/jimm")
|> json_response(404)
assert response == %{"error" => "Not found"}
end
end end
test "404s a private notice", %{conn: conn} do describe "GET /notice/:id/embed_player" do
note_activity = insert(:direct_note_activity) test "render embed player", %{conn: conn} do
url = "/notice/#{note_activity.id}" note_activity = insert(:note_activity)
object = Pleroma.Object.normalize(note_activity)
conn = object_data =
conn Map.put(object.data, "attachment", [
|> get(url) %{
"url" => [
%{
"href" =>
"https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
"mediaType" => "video/mp4",
"type" => "Link"
}
]
}
])
assert response(conn, 404) object
end |> Ecto.Changeset.change(data: object_data)
|> Pleroma.Repo.update()
test "404s a nonexisting notice", %{conn: conn} do conn =
url = "/notice/123" conn
|> get("/notice/#{note_activity.id}/embed_player")
conn = assert Plug.Conn.get_resp_header(conn, "x-frame-options") == ["ALLOW"]
conn
|> get(url)
assert response(conn, 404) assert Plug.Conn.get_resp_header(
conn,
"content-security-policy"
) == [
"default-src 'none';style-src 'self' 'unsafe-inline';img-src 'self' data: https:; media-src 'self' https:;"
]
assert response(conn, 200) =~
"<video controls loop><source src=\"https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4\" type=\"video/mp4\">Your browser does not support video/mp4 playback.</video>"
end
test "404s when activity isn't create", %{conn: conn} do
note_activity = insert(:note_activity, data_attrs: %{"type" => "Like"})
assert conn
|> get("/notice/#{note_activity.id}/embed_player")
|> response(404)
end
test "404s when activity is direct message", %{conn: conn} do
note_activity = insert(:note_activity, data_attrs: %{"directMessage" => true})
assert conn
|> get("/notice/#{note_activity.id}/embed_player")
|> response(404)
end
test "404s when attachment is empty", %{conn: conn} do
note_activity = insert(:note_activity)
object = Pleroma.Object.normalize(note_activity)
object_data = Map.put(object.data, "attachment", [])
object
|> Ecto.Changeset.change(data: object_data)
|> Pleroma.Repo.update()
assert conn
|> get("/notice/#{note_activity.id}/embed_player")
|> response(404)
end
test "404s when attachment isn't audio or video", %{conn: conn} do
note_activity = insert(:note_activity)
object = Pleroma.Object.normalize(note_activity)
object_data =
Map.put(object.data, "attachment", [
%{
"url" => [
%{
"href" => "https://peertube.moe/static/webseed/480.jpg",
"mediaType" => "image/jpg",
"type" => "Link"
}
]
}
])
object
|> Ecto.Changeset.change(data: object_data)
|> Pleroma.Repo.update()
assert conn
|> get("/notice/#{note_activity.id}/embed_player")
|> response(404)
end
end end
end end

View file

@ -124,8 +124,7 @@ test "renders body for follow activity" do
{:ok, _, _, activity} = CommonAPI.follow(user, other_user) {:ok, _, _, activity} = CommonAPI.follow(user, other_user)
object = Object.normalize(activity) object = Object.normalize(activity)
assert Impl.format_body(%{activity: activity}, user, object) == assert Impl.format_body(%{activity: activity}, user, object) == "@Bob has followed you"
"@Bob has followed you"
end end
test "renders body for announce activity" do test "renders body for announce activity" do
@ -156,7 +155,6 @@ test "renders body for like activity" do
{:ok, activity, _} = CommonAPI.favorite(activity.id, user) {:ok, activity, _} = CommonAPI.favorite(activity.id, user)
object = Object.normalize(activity) object = Object.normalize(activity)
assert Impl.format_body(%{activity: activity}, user, object) == assert Impl.format_body(%{activity: activity}, user, object) == "@Bob has favorited your post"
"@Bob has favorited your post"
end end
end end