Merge branch 'develop' into feature/reports-groups-and-multiple-state-update

This commit is contained in:
Maxim Filippov 2019-10-07 15:41:41 +03:00
commit 35068baf65
148 changed files with 2629 additions and 1322 deletions

View file

@ -8,18 +8,7 @@ defmodule Mix.Tasks.Pleroma.Config do
alias Pleroma.Repo
alias Pleroma.Web.AdminAPI.Config
@shortdoc "Manages the location of the config"
@moduledoc """
Manages the location of the config.
## Transfers config from file to DB.
mix pleroma.config migrate_to_db
## Transfers config from DB to file `config/env.exported_from_db.secret.exs`
mix pleroma.config migrate_from_db ENV
"""
@moduledoc File.read!("docs/administration/CLI_tasks/config.md")
def run(["migrate_to_db"]) do
start_pleroma()

View file

@ -13,34 +13,8 @@ defmodule Mix.Tasks.Pleroma.Database do
use Mix.Task
@shortdoc "A collection of database related tasks"
@moduledoc """
A collection of database related tasks
@moduledoc File.read!("docs/administration/CLI_tasks/database.md")
## Replace embedded objects with their references
Replaces embedded objects with references to them in the `objects` table. Only needs to be ran once. The reason why this is not a migration is because it could significantly increase the database size after being ran, however after this `VACUUM FULL` will be able to reclaim about 20% (really depends on what is in the database, your mileage may vary) of the db size before the migration.
mix pleroma.database remove_embedded_objects
Options:
- `--vacuum` - run `VACUUM FULL` after the embedded objects are replaced with their references
## Prune old objects from the database
mix pleroma.database prune_objects
## Create a conversation for all existing DMs. Can be safely re-run.
mix pleroma.database bump_all_conversations
## Remove duplicated items from following and update followers count for all users
mix pleroma.database update_users_following_followers_counts
## Fix the pre-existing "likes" collections for all objects
mix pleroma.database fix_likes_collections
"""
def run(["remove_embedded_objects" | args]) do
{options, [], []} =
OptionParser.parse(

View file

@ -2,16 +2,8 @@ defmodule Mix.Tasks.Pleroma.Digest do
use Mix.Task
@shortdoc "Manages digest emails"
@moduledoc """
Manages digest emails
@moduledoc File.read!("docs/administration/CLI_tasks/digest.md")
## Send digest email since given date (user registration date by default)
ignoring user activity status.
``mix pleroma.digest test <nickname> <since_date>``
Example: ``mix pleroma.digest test donaldtheduck 2019-05-20``
"""
def run(["test", nickname | opts]) do
Mix.Pleroma.start_pleroma()

View file

@ -6,54 +6,7 @@ defmodule Mix.Tasks.Pleroma.Emoji do
use Mix.Task
@shortdoc "Manages emoji packs"
@moduledoc """
Manages emoji packs
## ls-packs
mix pleroma.emoji ls-packs [OPTION...]
Lists the emoji packs and metadata specified in the manifest.
### Options
- `-m, --manifest PATH/URL` - path to a custom manifest, it can
either be an URL starting with `http`, in that case the
manifest will be fetched from that address, or a local path
## get-packs
mix pleroma.emoji get-packs [OPTION...] PACKS
Fetches, verifies and installs the specified PACKS from the
manifest into the `STATIC-DIR/emoji/PACK-NAME`
### Options
- `-m, --manifest PATH/URL` - same as ls-packs
## gen-pack
mix pleroma.emoji gen-pack PACK-URL
Creates a new manifest entry and a file list from the specified
remote pack file. Currently, only .zip archives are recognized
as remote pack files and packs are therefore assumed to be zip
archives. This command is intended to run interactively and will
first ask you some basic questions about the pack, then download
the remote file and generate an SHA256 checksum for it, then
generate an emoji file list for you.
The manifest entry will either be written to a newly created
`index.json` file or appended to the existing one, *replacing*
the old pack with the same name if it was in the file previously.
The file list will be written to the file specified previously,
*replacing* that file. You _should_ check that the file list doesn't
contain anything you don't need in the pack, that is, anything that is
not an emoji (the whole pack is downloaded, but only emoji files
are extracted).
"""
@moduledoc File.read!("docs/administration/CLI_tasks/emoji.md")
def run(["ls-packs" | args]) do
Application.ensure_all_started(:hackney)

View file

@ -7,36 +7,7 @@ defmodule Mix.Tasks.Pleroma.Instance do
import Mix.Pleroma
@shortdoc "Manages Pleroma instance"
@moduledoc """
Manages Pleroma instance.
## Generate a new instance config.
mix pleroma.instance gen [OPTION...]
If any options are left unspecified, you will be prompted interactively
## Options
- `-f`, `--force` - overwrite any output files
- `-o PATH`, `--output PATH` - the output file for the generated configuration
- `--output-psql PATH` - the output file for the generated PostgreSQL setup
- `--domain DOMAIN` - the domain of your instance
- `--instance-name INSTANCE_NAME` - the name of your instance
- `--admin-email ADMIN_EMAIL` - the email address of the instance admin
- `--notify-email NOTIFY_EMAIL` - email address for notifications
- `--dbhost HOSTNAME` - the hostname of the PostgreSQL database to use
- `--dbname DBNAME` - the name of the database to use
- `--dbuser DBUSER` - the user (aka role) to use for the database connection
- `--dbpass DBPASS` - the password to use for the database connection
- `--rum Y/N` - Whether to enable RUM indexes
- `--indexable Y/N` - Allow/disallow indexing site by search engines
- `--db-configurable Y/N` - Allow/disallow configuring instance from admin part
- `--uploads-dir` - the directory uploads go in when using a local uploader
- `--static-dir` - the directory custom public files should be read from (custom emojis, frontend bundle overrides, robots.txt, etc.)
- `--listen-ip` - the ip the app should listen to, defaults to 127.0.0.1
- `--listen-port` - the port the app should listen to, defaults to 4000
"""
@moduledoc File.read!("docs/administration/CLI_tasks/instance.md")
def run(["gen" | rest]) do
{options, [], []} =

View file

@ -9,25 +9,8 @@ defmodule Mix.Tasks.Pleroma.Relay do
alias Pleroma.Web.ActivityPub.Relay
@shortdoc "Manages remote relays"
@moduledoc """
Manages remote relays
@moduledoc File.read!("docs/administration/CLI_tasks/relay.md")
## Follow a remote relay
``mix pleroma.relay follow <relay_url>``
Example: ``mix pleroma.relay follow https://example.org/relay``
## Unfollow a remote relay
``mix pleroma.relay unfollow <relay_url>``
Example: ``mix pleroma.relay unfollow https://example.org/relay``
## List relay subscriptions
``mix pleroma.relay list``
"""
def run(["follow", target]) do
start_pleroma()

View file

@ -12,16 +12,8 @@ defmodule Mix.Tasks.Pleroma.Uploads do
@log_every 50
@shortdoc "Migrates uploads from local to remote storage"
@moduledoc """
Manages uploads
@moduledoc File.read!("docs/administration/CLI_tasks/uploads.md")
## Migrate uploads from local to remote storage
mix pleroma.uploads migrate_local TARGET_UPLOADER [OPTIONS...]
Options:
- `--delete` - delete local uploads after migrating them to the target uploader
A list of available uploaders can be seen in config.exs
"""
def run(["migrate_local", target_uploader | args]) do
delete? = Enum.member?(args, "--delete")
start_pleroma()

View file

@ -10,86 +10,8 @@ defmodule Mix.Tasks.Pleroma.User do
alias Pleroma.Web.OAuth
@shortdoc "Manages Pleroma users"
@moduledoc """
Manages Pleroma users.
@moduledoc File.read!("docs/administration/CLI_tasks/user.md")
## Create a new user.
mix pleroma.user new NICKNAME EMAIL [OPTION...]
Options:
- `--name NAME` - the user's name (i.e., "Lain Iwakura")
- `--bio BIO` - the user's bio
- `--password PASSWORD` - the user's password
- `--moderator`/`--no-moderator` - whether the user is a moderator
- `--admin`/`--no-admin` - whether the user is an admin
- `-y`, `--assume-yes`/`--no-assume-yes` - whether to assume yes to all questions
## Generate an invite link.
mix pleroma.user invite [OPTION...]
Options:
- `--expires-at DATE` - last day on which token is active (e.g. "2019-04-05")
- `--max-use NUMBER` - maximum numbers of token uses
## List generated invites
mix pleroma.user invites
## Revoke invite
mix pleroma.user revoke_invite TOKEN OR TOKEN_ID
## Delete the user's account.
mix pleroma.user rm NICKNAME
## Delete the user's activities.
mix pleroma.user delete_activities NICKNAME
## Sign user out from all applications (delete user's OAuth tokens and authorizations).
mix pleroma.user sign_out NICKNAME
## Deactivate or activate the user's account.
mix pleroma.user toggle_activated NICKNAME
## Unsubscribe local users from user's account and deactivate it
mix pleroma.user unsubscribe NICKNAME
## Unsubscribe local users from an entire instance and deactivate all accounts
mix pleroma.user unsubscribe_all_from_instance INSTANCE
## Create a password reset link.
mix pleroma.user reset_password NICKNAME
## Set the value of the given user's settings.
mix pleroma.user set NICKNAME [OPTION...]
Options:
- `--locked`/`--no-locked` - whether the user's account is locked
- `--moderator`/`--no-moderator` - whether the user is a moderator
- `--admin`/`--no-admin` - whether the user is an admin
## Add tags to a user.
mix pleroma.user tag NICKNAME TAGS
## Delete tags from a user.
mix pleroma.user untag NICKNAME TAGS
## Toggle confirmation of the user's account.
mix pleroma.user toggle_confirmed NICKNAME
"""
def run(["new", nickname, email | rest]) do
{options, [], []} =
OptionParser.parse(

View file

@ -42,6 +42,7 @@ defmodule Pleroma.Application do
hackney_pool_children() ++
[
Pleroma.Stats,
Pleroma.JobQueueMonitor,
{Oban, Pleroma.Config.get(Oban)}
] ++
task_children(@env) ++

View file

@ -67,6 +67,8 @@ defmodule Pleroma.Conversation do
participations =
Enum.map(users, fn user ->
User.increment_unread_conversation_count(conversation, user)
{:ok, participation} =
Participation.create_for_user_and_conversation(user, conversation, opts)

View file

@ -52,6 +52,15 @@ defmodule Pleroma.Conversation.Participation do
participation
|> read_cng(%{read: true})
|> Repo.update()
|> case do
{:ok, participation} ->
participation = Repo.preload(participation, :user)
User.set_unread_conversation_count(participation.user)
{:ok, participation}
error ->
error
end
end
def mark_as_unread(participation) do
@ -135,4 +144,12 @@ defmodule Pleroma.Conversation.Participation do
{:ok, Repo.preload(participation, :recipients, force: true)}
end
def unread_conversation_count_for_user(user) do
from(p in __MODULE__,
where: p.user_id == ^user.id,
where: not p.read,
select: %{count: count(p.id)}
)
end
end

View file

@ -17,7 +17,7 @@ defmodule Pleroma.Emails.AdminEmail do
end
defp user_url(user) do
Helpers.o_status_url(Pleroma.Web.Endpoint, :feed_redirect, user.nickname)
Helpers.feed_url(Pleroma.Web.Endpoint, :feed_redirect, user.id)
end
def report(to, reporter, account, statuses, comment) do

View file

@ -14,6 +14,7 @@ defmodule Pleroma.Healthcheck do
active: 0,
idle: 0,
memory_used: 0,
job_queue_stats: nil,
healthy: true
@type t :: %__MODULE__{
@ -21,6 +22,7 @@ defmodule Pleroma.Healthcheck do
active: non_neg_integer(),
idle: non_neg_integer(),
memory_used: number(),
job_queue_stats: map(),
healthy: boolean()
}
@ -30,6 +32,7 @@ defmodule Pleroma.Healthcheck do
memory_used: Float.round(:erlang.memory(:total) / 1024 / 1024, 2)
}
|> assign_db_info()
|> assign_job_queue_stats()
|> check_health()
end
@ -55,6 +58,11 @@ defmodule Pleroma.Healthcheck do
Map.merge(healthcheck, db_info)
end
defp assign_job_queue_stats(healthcheck) do
stats = Pleroma.JobQueueMonitor.stats()
Map.put(healthcheck, :job_queue_stats, stats)
end
@spec check_health(Healthcheck.t()) :: Healthcheck.t()
def check_health(%{pool_size: pool_size, active: active} = check)
when active >= pool_size do

View file

@ -0,0 +1,78 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.JobQueueMonitor do
use GenServer
@initial_state %{workers: %{}, queues: %{}, processed_jobs: 0}
@queue %{processed_jobs: 0, success: 0, failure: 0}
@operation %{processed_jobs: 0, success: 0, failure: 0}
def start_link(_) do
GenServer.start_link(__MODULE__, @initial_state, name: __MODULE__)
end
@impl true
def init(state) do
:telemetry.attach("oban-monitor-failure", [:oban, :failure], &handle_event/4, nil)
:telemetry.attach("oban-monitor-success", [:oban, :success], &handle_event/4, nil)
{:ok, state}
end
def stats do
GenServer.call(__MODULE__, :stats)
end
def handle_event([:oban, status], %{duration: duration}, meta, _) do
GenServer.cast(__MODULE__, {:process_event, status, duration, meta})
end
@impl true
def handle_call(:stats, _from, state) do
{:reply, state, state}
end
@impl true
def handle_cast({:process_event, status, duration, meta}, state) do
state =
state
|> Map.update!(:workers, fn workers ->
workers
|> Map.put_new(meta.worker, %{})
|> Map.update!(meta.worker, &update_worker(&1, status, meta, duration))
end)
|> Map.update!(:queues, fn workers ->
workers
|> Map.put_new(meta.queue, @queue)
|> Map.update!(meta.queue, &update_queue(&1, status, meta, duration))
end)
|> Map.update!(:processed_jobs, &(&1 + 1))
{:noreply, state}
end
defp update_worker(worker, status, meta, duration) do
worker
|> Map.put_new(meta.args["op"], @operation)
|> Map.update!(meta.args["op"], &update_op(&1, status, meta, duration))
end
defp update_op(op, :enqueue, _meta, _duration) do
op
|> Map.update!(:enqueued, &(&1 + 1))
end
defp update_op(op, status, _meta, _duration) do
op
|> Map.update!(:processed_jobs, &(&1 + 1))
|> Map.update!(status, &(&1 + 1))
end
defp update_queue(queue, status, _meta, _duration) do
queue
|> Map.update!(:processed_jobs, &(&1 + 1))
|> Map.update!(status, &(&1 + 1))
end
end

View file

@ -6,6 +6,8 @@ defmodule Pleroma.Plugs.OAuthScopesPlug do
import Plug.Conn
import Pleroma.Web.Gettext
alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
@behaviour Plug
def init(%{scopes: _} = options), do: options
@ -13,24 +15,26 @@ defmodule Pleroma.Plugs.OAuthScopesPlug do
def call(%Plug.Conn{assigns: assigns} = conn, %{scopes: scopes} = options) do
op = options[:op] || :|
token = assigns[:token]
matched_scopes = token && filter_descendants(scopes, token.scopes)
cond do
is_nil(token) ->
maybe_perform_instance_privacy_check(conn, options)
op == :| && Enum.any?(matched_scopes) ->
conn
op == :| && scopes -- token.scopes != scopes ->
conn
op == :& && scopes -- token.scopes == [] ->
op == :& && matched_scopes == scopes ->
conn
options[:fallback] == :proceed_unauthenticated ->
conn
|> assign(:user, nil)
|> assign(:token, nil)
|> maybe_perform_instance_privacy_check(options)
true ->
missing_scopes = scopes -- token.scopes
missing_scopes = scopes -- matched_scopes
permissions = Enum.join(missing_scopes, " #{op} ")
error_message =
@ -42,4 +46,25 @@ defmodule Pleroma.Plugs.OAuthScopesPlug do
|> halt()
end
end
@doc "Filters descendants of supported scopes"
def filter_descendants(scopes, supported_scopes) do
Enum.filter(
scopes,
fn scope ->
Enum.find(
supported_scopes,
&(scope == &1 || String.starts_with?(scope, &1 <> ":"))
)
end
)
end
defp maybe_perform_instance_privacy_check(%Plug.Conn{} = conn, options) do
if options[:skip_instance_privacy_check] do
conn
else
EnsurePublicOrAuthenticatedPlug.call(conn, [])
end
end
end

View file

@ -48,7 +48,7 @@ defmodule Pleroma.Signature do
end
def sign(%User{} = user, headers) do
with {:ok, %{info: %{keys: keys}}} <- User.ensure_keys_present(user),
with {:ok, %{keys: keys}} <- User.ensure_keys_present(user),
{:ok, private_key, _} <- Keys.keys_from_pem(keys) do
HTTPSignatures.sign(private_key, user.ap_id <> "#main-key", headers)
end

View file

@ -11,6 +11,7 @@ defmodule Pleroma.User do
alias Comeonin.Pbkdf2
alias Ecto.Multi
alias Pleroma.Activity
alias Pleroma.Conversation.Participation
alias Pleroma.Delivery
alias Pleroma.Keys
alias Pleroma.Notification
@ -50,6 +51,7 @@ defmodule Pleroma.User do
field(:password_hash, :string)
field(:password, :string, virtual: true)
field(:password_confirmation, :string, virtual: true)
field(:keys, :string)
field(:following, {:array, :string}, default: [])
field(:ap_id, :string)
field(:avatar, :map)
@ -583,7 +585,7 @@ defmodule Pleroma.User do
is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
restrict_to_local == false ->
restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
get_cached_by_nickname(nickname_or_id)
restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
@ -842,6 +844,61 @@ defmodule Pleroma.User do
def maybe_update_following_count(user), do: user
def set_unread_conversation_count(%User{local: true} = user) do
unread_query = Participation.unread_conversation_count_for_user(user)
User
|> join(:inner, [u], p in subquery(unread_query))
|> update([u, p],
set: [
info:
fragment(
"jsonb_set(?, '{unread_conversation_count}', ?::varchar::jsonb, true)",
u.info,
p.count
)
]
)
|> where([u], u.id == ^user.id)
|> select([u], u)
|> Repo.update_all([])
|> case do
{1, [user]} -> set_cache(user)
_ -> {:error, user}
end
end
def set_unread_conversation_count(_), do: :noop
def increment_unread_conversation_count(conversation, %User{local: true} = user) do
unread_query =
Participation.unread_conversation_count_for_user(user)
|> where([p], p.conversation_id == ^conversation.id)
User
|> join(:inner, [u], p in subquery(unread_query))
|> update([u, p],
set: [
info:
fragment(
"jsonb_set(?, '{unread_conversation_count}', (coalesce((?->>'unread_conversation_count')::int, 0) + 1)::varchar::jsonb, true)",
u.info,
u.info
)
]
)
|> where([u], u.id == ^user.id)
|> where([u, p], p.count == 0)
|> select([u], u)
|> Repo.update_all([])
|> case do
{1, [user]} -> set_cache(user)
_ -> {:error, user}
end
end
def increment_unread_conversation_count(_, _), do: :noop
def remove_duplicated_following(%User{following: following} = user) do
uniq_following = Enum.uniq(following)
@ -1498,11 +1555,14 @@ defmodule Pleroma.User do
}
end
def ensure_keys_present(%{info: %{keys: keys}} = user) when not is_nil(keys), do: {:ok, user}
def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
def ensure_keys_present(%User{} = user) do
with {:ok, pem} <- Keys.generate_rsa_pem() do
update_info(user, &User.Info.set_keys(&1, pem))
user
|> cast(%{keys: pem}, [:keys])
|> validate_required([:keys])
|> update_and_set_cache()
end
end

View file

@ -47,6 +47,7 @@ defmodule Pleroma.User.Info do
field(:hide_followers, :boolean, default: false)
field(:hide_follows, :boolean, default: false)
field(:hide_favorites, :boolean, default: true)
field(:unread_conversation_count, :integer, default: 0)
field(:pinned_activities, {:array, :string}, default: [])
field(:email_notifications, :map, default: %{"digest" => false})
field(:mascot, :map, default: nil)

View file

@ -17,6 +17,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
alias Pleroma.User
alias Pleroma.Web.ActivityPub.MRF
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.Streamer
alias Pleroma.Web.WebFinger
alias Pleroma.Workers.BackgroundWorker
@ -291,8 +292,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
def update(%{to: to, cc: cc, actor: actor, object: object} = params) do
# only accept false as false value
local = !(params[:local] == false)
activity_id = params[:activity_id]
with data <- %{
"to" => to,
@ -301,6 +302,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
"actor" => actor,
"object" => object
},
data <- Utils.maybe_put(data, "id", activity_id),
{:ok, activity} <- insert(data, local),
:ok <- maybe_federate(activity) do
{:ok, activity}

View file

@ -82,38 +82,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
conn
end
def object_likes(conn, %{"uuid" => uuid, "page" => page}) do
with ap_id <- o_status_url(conn, :object, uuid),
%Object{} = object <- Object.get_cached_by_ap_id(ap_id),
{_, true} <- {:public?, Visibility.is_public?(object)},
likes <- Utils.get_object_likes(object) do
{page, _} = Integer.parse(page)
conn
|> put_resp_content_type("application/activity+json")
|> put_view(ObjectView)
|> render("likes.json", %{ap_id: ap_id, likes: likes, page: page})
else
{:public?, false} ->
{:error, :not_found}
end
end
def object_likes(conn, %{"uuid" => uuid}) do
with ap_id <- o_status_url(conn, :object, uuid),
%Object{} = object <- Object.get_cached_by_ap_id(ap_id),
{_, true} <- {:public?, Visibility.is_public?(object)},
likes <- Utils.get_object_likes(object) do
conn
|> put_resp_content_type("application/activity+json")
|> put_view(ObjectView)
|> render("likes.json", %{ap_id: ap_id, likes: likes})
else
{:public?, false} ->
{:error, :not_found}
end
end
def activity(conn, %{"uuid" => uuid}) do
with ap_id <- o_status_url(conn, :activity, uuid),
%Activity{} = activity <- Activity.normalize(ap_id),

View file

@ -168,7 +168,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
when obj_type in ["Application", "Group", "Organization", "Person", "Service"] do
actor_info = URI.parse(actor)
with {:ok, object} <- check_avatar_removal(actor_info, object),
with {:ok, object} <- check_accept(actor_info, object),
{:ok, object} <- check_reject(actor_info, object),
{:ok, object} <- check_avatar_removal(actor_info, object),
{:ok, object} <- check_banner_removal(actor_info, object) do
{:ok, object}
else

View file

@ -580,7 +580,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
) do
with actor <- Containment.get_actor(data),
{:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor),
{:ok, object} <- get_obj_helper(object_id),
{:ok, object} <- get_embedded_obj_helper(object_id, actor),
public <- Visibility.is_public?(data),
{:ok, activity, _object} <- ActivityPub.announce(actor, object, id, false, public) do
{:ok, activity}
@ -621,7 +621,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
to: data["to"] || [],
cc: data["cc"] || [],
object: object,
actor: actor_id
actor: actor_id,
activity_id: data["id"]
})
else
e ->
@ -781,6 +782,29 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
end
@spec get_embedded_obj_helper(String.t() | Object.t(), User.t()) :: {:ok, Object.t()} | nil
def get_embedded_obj_helper(%{"attributedTo" => attributed_to, "id" => object_id} = data, %User{
ap_id: ap_id
})
when attributed_to == ap_id do
with {:ok, activity} <-
handle_incoming(%{
"type" => "Create",
"to" => data["to"],
"cc" => data["cc"],
"actor" => attributed_to,
"object" => data
}) do
{:ok, Object.normalize(activity)}
else
_ -> get_obj_helper(object_id)
end
end
def get_embedded_obj_helper(object_id, _) do
get_obj_helper(object_id)
end
def set_reply_to_uri(%{"inReplyTo" => in_reply_to} = object) when is_binary(in_reply_to) do
with false <- String.starts_with?(in_reply_to, "http"),
{:ok, %{data: replied_to_object}} <- get_obj_helper(in_reply_to) do

View file

@ -253,16 +253,6 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|> Repo.one()
end
@doc """
Returns like activities targeting an object
"""
def get_object_likes(%{data: %{"id" => id}}) do
id
|> Activity.Queries.by_object_id()
|> Activity.Queries.by_type("Like")
|> Repo.all()
end
@spec make_like_data(User.t(), map(), String.t()) :: map()
def make_like_data(
%User{ap_id: ap_id} = actor,
@ -463,14 +453,16 @@ defmodule Pleroma.Web.ActivityPub.Utils do
"""
def make_unannounce_data(
%User{ap_id: ap_id} = user,
%Activity{data: %{"context" => context}} = activity,
%Activity{data: %{"context" => context, "object" => object}} = activity,
activity_id
) do
object = Object.normalize(object)
%{
"type" => "Undo",
"actor" => ap_id,
"object" => activity.data,
"to" => [user.follower_address, activity.data["actor"]],
"to" => [user.follower_address, object.data["actor"]],
"cc" => [Pleroma.Constants.as_public()],
"context" => context
}
@ -479,14 +471,16 @@ defmodule Pleroma.Web.ActivityPub.Utils do
def make_unlike_data(
%User{ap_id: ap_id} = user,
%Activity{data: %{"context" => context}} = activity,
%Activity{data: %{"context" => context, "object" => object}} = activity,
activity_id
) do
object = Object.normalize(object)
%{
"type" => "Undo",
"actor" => ap_id,
"object" => activity.data,
"to" => [user.follower_address, activity.data["actor"]],
"to" => [user.follower_address, object.data["actor"]],
"cc" => [Pleroma.Constants.as_public()],
"context" => context
}
@ -846,6 +840,6 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|> Repo.all()
end
defp maybe_put(map, _key, nil), do: map
defp maybe_put(map, key, value), do: Map.put(map, key, value)
def maybe_put(map, _key, nil), do: map
def maybe_put(map, key, value), do: Map.put(map, key, value)
end

View file

@ -37,40 +37,4 @@ defmodule Pleroma.Web.ActivityPub.ObjectView do
Map.merge(base, additional)
end
def render("likes.json", %{ap_id: ap_id, likes: likes, page: page}) do
collection(likes, "#{ap_id}/likes", page)
|> Map.merge(Pleroma.Web.ActivityPub.Utils.make_json_ld_header())
end
def render("likes.json", %{ap_id: ap_id, likes: likes}) do
%{
"id" => "#{ap_id}/likes",
"type" => "OrderedCollection",
"totalItems" => length(likes),
"first" => collection(likes, "#{ap_id}/likes", 1)
}
|> Map.merge(Pleroma.Web.ActivityPub.Utils.make_json_ld_header())
end
def collection(collection, iri, page) do
offset = (page - 1) * 10
items = Enum.slice(collection, offset, 10)
items = Enum.map(items, fn object -> Transmogrifier.prepare_object(object.data) end)
total = length(collection)
map = %{
"id" => "#{iri}?page=#{page}",
"type" => "OrderedCollectionPage",
"partOf" => iri,
"totalItems" => total,
"orderedItems" => items
}
if offset + length(items) < total do
Map.put(map, "next", "#{iri}?page=#{page + 1}")
else
map
end
end
end

View file

@ -33,7 +33,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
def render("service.json", %{user: user}) do
{:ok, user} = User.ensure_keys_present(user)
{:ok, _, public_key} = Keys.keys_from_pem(user.info.keys)
{:ok, _, public_key} = Keys.keys_from_pem(user.keys)
public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
public_key = :public_key.pem_encode([public_key])
@ -69,7 +69,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
def render("user.json", %{user: user}) do
{:ok, user} = User.ensure_keys_present(user)
{:ok, _, public_key} = Keys.keys_from_pem(user.info.keys)
{:ok, _, public_key} = Keys.keys_from_pem(user.keys)
public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
public_key = :public_key.pem_encode([public_key])

View file

@ -6,6 +6,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
use Pleroma.Web, :controller
alias Pleroma.Activity
alias Pleroma.ModerationLog
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User
alias Pleroma.UserInviteToken
alias Pleroma.Web.ActivityPub.ActivityPub
@ -27,6 +28,67 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
require Logger
plug(
OAuthScopesPlug,
%{scopes: ["read:accounts"]}
when action in [:list_users, :user_show, :right_get, :invites]
)
plug(
OAuthScopesPlug,
%{scopes: ["write:accounts"]}
when action in [
:get_invite_token,
:revoke_invite,
:email_invite,
:get_password_reset,
:user_follow,
:user_unfollow,
:user_delete,
:users_create,
:user_toggle_activation,
:tag_users,
:untag_users,
:right_add,
:right_delete,
:set_activation_status
]
)
plug(
OAuthScopesPlug,
%{scopes: ["read:reports"]} when action in [:list_reports, :report_show]
)
plug(
OAuthScopesPlug,
%{scopes: ["write:reports"]}
when action in [:report_update_state, :report_respond]
)
plug(
OAuthScopesPlug,
%{scopes: ["read:statuses"]} when action == :list_user_statuses
)
plug(
OAuthScopesPlug,
%{scopes: ["write:statuses"]}
when action in [:status_update, :status_delete]
)
plug(
OAuthScopesPlug,
%{scopes: ["read"]}
when action in [:config_show, :migrate_to_db, :migrate_from_db, :list_log]
)
plug(
OAuthScopesPlug,
%{scopes: ["write"]}
when action in [:relay_follow, :relay_unfollow, :config_update]
)
@users_page_size 50
action_fallback(:errors)

View file

@ -16,6 +16,8 @@ defmodule Pleroma.Web.CommonAPI do
import Pleroma.Web.Gettext
import Pleroma.Web.CommonAPI.Utils
require Pleroma.Constants
def follow(follower, followed) do
timeout = Pleroma.Config.get([:activitypub, :follow_handshake_timeout])
@ -271,7 +273,7 @@ defmodule Pleroma.Web.CommonAPI do
ActivityPub.update(%{
local: true,
to: [user.follower_address],
to: [Pleroma.Constants.as_public(), user.follower_address],
cc: [],
actor: user.ap_id,
object: Pleroma.Web.ActivityPub.UserView.render("user.json", %{user: user})

View file

@ -0,0 +1,63 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Feed.FeedController do
use Pleroma.Web, :controller
alias Fallback.RedirectController
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.ActivityPubController
plug(Pleroma.Plugs.SetFormatPlug when action in [:feed_redirect])
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
with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do
redirect(conn, external: "#{feed_url(conn, :feed, user.nickname)}.atom")
end
end
def feed(conn, %{"nickname" => nickname} = params) do
with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do
query_params =
params
|> Map.take(["max_id"])
|> Map.put("type", ["Create"])
|> Map.put("whole_db", true)
|> Map.put("actor_id", user.ap_id)
activities =
query_params
|> ActivityPub.fetch_public_activities()
|> Enum.reverse()
conn
|> put_resp_content_type("application/atom+xml")
|> render("feed.xml", user: user, activities: activities)
end
end
def errors(conn, {:error, :not_found}) do
render_error(conn, :not_found, "Not found")
end
def errors(conn, {:fetch_user, nil}), do: errors(conn, {:error, :not_found})
def errors(conn, _) do
render_error(conn, :internal_server_error, "Something went wrong")
end
end

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 Pleroma.Web.Feed.FeedView do
use Phoenix.HTML
use Pleroma.Web, :view
alias Pleroma.Object
alias Pleroma.User
alias Pleroma.Web.MediaProxy
require Pleroma.Constants
def most_recent_update(activities, user) do
(List.first(activities) || user).updated_at
|> NaiveDateTime.to_iso8601()
end
def logo(user) do
user
|> User.avatar_url()
|> MediaProxy.url()
end
def last_activity(activities) do
List.last(activities)
end
def activity_object(activity) do
Object.normalize(activity)
end
def activity_object_data(activity) do
activity
|> activity_object()
|> Map.get(:data)
end
def activity_content(activity) do
content = activity_object_data(activity)["content"]
content
|> String.replace(~r/[\n\r]/, "")
|> escape()
end
def activity_context(activity) do
activity.data["context"]
end
def attachment_href(attachment) do
attachment["url"]
|> hd()
|> Map.get("href")
end
def attachment_type(attachment) do
attachment["url"]
|> hd()
|> Map.get("mediaType")
end
def get_href(id) do
with %Object{data: %{"external_url" => external_url}} <- Object.get_cached_by_ap_id(id) do
external_url
else
_e -> id
end
end
def escape(html) do
html
|> html_escape()
|> safe_to_string()
end
end

View file

@ -5,8 +5,20 @@
defmodule Pleroma.Web.MastoFEController do
use Pleroma.Web, :controller
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User
plug(OAuthScopesPlug, %{scopes: ["write:accounts"]} when action == :put_settings)
# Note: :index action handles attempt of unauthenticated access to private instance with redirect
plug(
OAuthScopesPlug,
%{scopes: ["read"], fallback: :proceed_unauthenticated, skip_instance_privacy_check: true}
when action == :index
)
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug when action != :index)
@doc "GET /web/*path"
def index(%{assigns: %{user: user}} = conn, _params) do
token = get_session(conn, :oauth_token)

View file

@ -9,6 +9,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
only: [add_link_headers: 2, truthy_param?: 1, assign_account_by_id: 2, json_response: 3]
alias Pleroma.Emoji
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Plugs.RateLimiter
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
@ -19,6 +20,49 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
alias Pleroma.Web.OAuth.Token
alias Pleroma.Web.TwitterAPI.TwitterAPI
plug(
OAuthScopesPlug,
%{fallback: :proceed_unauthenticated, scopes: ["read:accounts"]}
when action == :show
)
plug(
OAuthScopesPlug,
%{scopes: ["read:accounts"]}
when action in [:endorsements, :verify_credentials, :followers, :following]
)
plug(OAuthScopesPlug, %{scopes: ["write:accounts"]} when action == :update_credentials)
plug(OAuthScopesPlug, %{scopes: ["read:lists"]} when action == :lists)
plug(
OAuthScopesPlug,
%{scopes: ["follow", "read:blocks"]} when action == :blocks
)
plug(
OAuthScopesPlug,
%{scopes: ["follow", "write:blocks"]} when action in [:block, :unblock]
)
plug(OAuthScopesPlug, %{scopes: ["read:follows"]} when action == :relationships)
# Note: :follows (POST /api/v1/follows) is the same as :follow, consider removing :follows
plug(
OAuthScopesPlug,
%{scopes: ["follow", "write:follows"]} when action in [:follows, :follow, :unfollow]
)
plug(OAuthScopesPlug, %{scopes: ["follow", "read:mutes"]} when action == :mutes)
plug(OAuthScopesPlug, %{scopes: ["follow", "write:mutes"]} when action in [:mute, :unmute])
plug(
Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
when action != :create
)
@relations [:follow, :unfollow]
@needs_account ~W(followers following lists follow unfollow mute unmute block unblock)a
@ -105,6 +149,17 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|> Enum.concat(Emoji.Formatter.get_emoji_map(emojis_text))
|> Enum.dedup()
params =
if Map.has_key?(params, "fields_attributes") do
Map.update!(params, "fields_attributes", fn fields ->
fields
|> normalize_fields_attributes()
|> Enum.filter(fn %{"name" => n} -> n != "" end)
end)
else
params
end
info_params =
[
:no_rich_text,
@ -122,12 +177,12 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
add_if_present(acc, params, to_string(key), key, &{:ok, truthy_param?(&1)})
end)
|> add_if_present(params, "default_scope", :default_scope)
|> add_if_present(params, "fields", :fields, fn fields ->
|> add_if_present(params, "fields_attributes", :fields, fn fields ->
fields = Enum.map(fields, fn f -> Map.update!(f, "value", &AutoLinker.link(&1)) end)
{:ok, fields}
end)
|> add_if_present(params, "fields", :raw_fields)
|> add_if_present(params, "fields_attributes", :raw_fields)
|> add_if_present(params, "pleroma_settings_store", :pleroma_settings_store, fn value ->
{:ok, Map.merge(user.info.pleroma_settings_store, value)}
end)
@ -168,6 +223,14 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
end
end
defp normalize_fields_attributes(fields) do
if Enum.all?(fields, &is_tuple/1) do
Enum.map(fields, fn {_, v} -> v end)
else
fields
end
end
@doc "GET /api/v1/accounts/relationships"
def relationships(%{assigns: %{user: user}} = conn, %{"id" => id}) do
targets = User.get_all_by_ids(List.wrap(id))
@ -301,4 +364,30 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
{:error, message} -> json_response(conn, :forbidden, %{error: message})
end
end
@doc "POST /api/v1/follows"
def follows(%{assigns: %{user: follower}} = conn, %{"uri" => uri}) do
with {_, %User{} = followed} <- {:followed, User.get_cached_by_nickname(uri)},
{_, true} <- {:followed, follower.id != followed.id},
{:ok, follower, followed, _} <- CommonAPI.follow(follower, followed) do
render(conn, "show.json", user: followed, for: follower)
else
{:followed, _} -> {:error, :not_found}
{:error, message} -> json_response(conn, :forbidden, %{error: message})
end
end
@doc "GET /api/v1/mutes"
def mutes(%{assigns: %{user: user}} = conn, _) do
render(conn, "index.json", users: User.muted_users(user), for: user, as: :user)
end
@doc "GET /api/v1/blocks"
def blocks(%{assigns: %{user: user}} = conn, _) do
render(conn, "index.json", users: User.blocked_users(user), for: user, as: :user)
end
@doc "GET /api/v1/endorsements"
def endorsements(conn, params),
do: Pleroma.Web.MastodonAPI.MastodonAPIController.empty_array(conn, params)
end

View file

@ -5,6 +5,7 @@
defmodule Pleroma.Web.MastodonAPI.AppController do
use Pleroma.Web, :controller
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Repo
alias Pleroma.Web.OAuth.App
alias Pleroma.Web.OAuth.Scopes
@ -12,6 +13,8 @@ defmodule Pleroma.Web.MastodonAPI.AppController do
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
plug(OAuthScopesPlug, %{scopes: ["read"]} when action == :verify_credentials)
@local_mastodon_name "Mastodon-Local"
@doc "POST /api/v1/apps"

View file

@ -8,10 +8,16 @@ defmodule Pleroma.Web.MastodonAPI.ConversationController do
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
alias Pleroma.Conversation.Participation
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Repo
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action == :index)
plug(OAuthScopesPlug, %{scopes: ["write:conversations"]} when action == :read)
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
@doc "GET /api/v1/conversations"
def index(%{assigns: %{user: user}} = conn, params) do
participations = Participation.for_user_with_last_activity_id(user, params)

View file

@ -5,8 +5,21 @@
defmodule Pleroma.Web.MastodonAPI.DomainBlockController do
use Pleroma.Web, :controller
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User
plug(
OAuthScopesPlug,
%{scopes: ["follow", "read:blocks"]} when action == :index
)
plug(
OAuthScopesPlug,
%{scopes: ["follow", "write:blocks"]} when action != :index
)
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
@doc "GET /api/v1/domain_blocks"
def index(%{assigns: %{user: %{info: info}}} = conn, _) do
json(conn, Map.get(info, :domain_blocks, []))

View file

@ -6,6 +6,18 @@ defmodule Pleroma.Web.MastodonAPI.FilterController do
use Pleroma.Web, :controller
alias Pleroma.Filter
alias Pleroma.Plugs.OAuthScopesPlug
@oauth_read_actions [:show, :index]
plug(OAuthScopesPlug, %{scopes: ["read:filters"]} when action in @oauth_read_actions)
plug(
OAuthScopesPlug,
%{scopes: ["write:filters"]} when action not in @oauth_read_actions
)
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
@doc "GET /api/v1/filters"
def index(%{assigns: %{user: user}} = conn, _) do

View file

@ -5,6 +5,7 @@
defmodule Pleroma.Web.MastodonAPI.FollowRequestController do
use Pleroma.Web, :controller
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User
alias Pleroma.Web.CommonAPI
@ -13,6 +14,15 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestController do
action_fallback(:errors)
plug(OAuthScopesPlug, %{scopes: ["follow", "read:follows"]} when action == :index)
plug(
OAuthScopesPlug,
%{scopes: ["follow", "write:follows"]} when action != :index
)
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
@doc "GET /api/v1/follow_requests"
def index(%{assigns: %{user: followed}} = conn, _params) do
follow_requests = User.get_follow_requests(followed)

View file

@ -5,11 +5,22 @@
defmodule Pleroma.Web.MastodonAPI.ListController do
use Pleroma.Web, :controller
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User
alias Pleroma.Web.MastodonAPI.AccountView
plug(:list_by_id_and_user when action not in [:index, :create])
plug(OAuthScopesPlug, %{scopes: ["read:lists"]} when action in [:index, :show, :list_accounts])
plug(
OAuthScopesPlug,
%{scopes: ["write:lists"]}
when action in [:create, :update, :delete, :add_to_list, :remove_from_list]
)
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
# GET /api/v1/lists

View file

@ -5,86 +5,10 @@
defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
use Pleroma.Web, :controller
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
alias Pleroma.Bookmark
alias Pleroma.Pagination
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.MastodonAPI.StatusView
require Logger
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
def follows(%{assigns: %{user: follower}} = conn, %{"uri" => uri}) do
with {_, %User{} = followed} <- {:followed, User.get_cached_by_nickname(uri)},
{_, true} <- {:followed, follower.id != followed.id},
{:ok, follower, followed, _} <- CommonAPI.follow(follower, followed) do
conn
|> put_view(AccountView)
|> render("show.json", %{user: followed, for: follower})
else
{:followed, _} ->
{:error, :not_found}
{:error, message} ->
conn
|> put_status(:forbidden)
|> json(%{error: message})
end
end
def mutes(%{assigns: %{user: user}} = conn, _) do
with muted_accounts <- User.muted_users(user) do
res = AccountView.render("index.json", users: muted_accounts, for: user, as: :user)
json(conn, res)
end
end
def blocks(%{assigns: %{user: user}} = conn, _) do
with blocked_accounts <- User.blocked_users(user) do
res = AccountView.render("index.json", users: blocked_accounts, for: user, as: :user)
json(conn, res)
end
end
def favourites(%{assigns: %{user: user}} = conn, params) do
params =
params
|> Map.put("type", "Create")
|> Map.put("favorited_by", user.ap_id)
|> Map.put("blocking_user", user)
activities =
ActivityPub.fetch_activities([], params)
|> Enum.reverse()
conn
|> add_link_headers(activities)
|> put_view(StatusView)
|> render("index.json", %{activities: activities, for: user, as: :activity})
end
def bookmarks(%{assigns: %{user: user}} = conn, params) do
user = User.get_cached_by_id(user.id)
bookmarks =
Bookmark.for_user_query(user.id)
|> Pagination.fetch_paginated(params)
activities =
bookmarks
|> Enum.map(fn b -> Map.put(b.activity, :bookmark, Map.delete(b, :activity)) end)
conn
|> add_link_headers(bookmarks)
|> put_view(StatusView)
|> render("index.json", %{activities: activities, for: user, as: :activity})
end
# Stubs for unimplemented mastodon api
#
def empty_array(conn, _) do

View file

@ -6,12 +6,17 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do
use Pleroma.Web, :controller
alias Pleroma.Object
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
plug(:put_view, Pleroma.Web.MastodonAPI.StatusView)
plug(OAuthScopesPlug, %{scopes: ["write:media"]})
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
@doc "POST /api/v1/media"
def create(%{assigns: %{user: user}} = conn, %{"file" => file} = data) do
with {:ok, object} <-

View file

@ -8,8 +8,20 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
alias Pleroma.Notification
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Web.MastodonAPI.MastodonAPI
@oauth_read_actions [:show, :index]
plug(
OAuthScopesPlug,
%{scopes: ["read:notifications"]} when action in @oauth_read_actions
)
plug(OAuthScopesPlug, %{scopes: ["write:notifications"]} when action not in @oauth_read_actions)
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
# GET /api/v1/notifications
def index(%{assigns: %{user: user}} = conn, params) do
notifications = MastodonAPI.get_notifications(user, params)

View file

@ -9,11 +9,21 @@ defmodule Pleroma.Web.MastodonAPI.PollController do
alias Pleroma.Activity
alias Pleroma.Object
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.CommonAPI
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
plug(
OAuthScopesPlug,
%{scopes: ["read:statuses"], fallback: :proceed_unauthenticated} when action == :show
)
plug(OAuthScopesPlug, %{scopes: ["write:statuses"]} when action == :vote)
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
@doc "GET /api/v1/polls/:id"
def show(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with %Object{} = object <- Object.get_by_id_and_maybe_refetch(id, interval: 60),

View file

@ -3,10 +3,16 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.ReportController do
alias Pleroma.Plugs.OAuthScopesPlug
use Pleroma.Web, :controller
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
plug(OAuthScopesPlug, %{scopes: ["write:reports"]} when action == :create)
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
@doc "POST /api/v1/reports"
def create(%{assigns: %{user: user}} = conn, params) do
with {:ok, activity} <- Pleroma.Web.CommonAPI.report(user, params) do

View file

@ -7,11 +7,19 @@ defmodule Pleroma.Web.MastodonAPI.ScheduledActivityController do
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.ScheduledActivity
alias Pleroma.Web.MastodonAPI.MastodonAPI
plug(:assign_scheduled_activity when action != :index)
@oauth_read_actions [:show, :index]
plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action in @oauth_read_actions)
plug(OAuthScopesPlug, %{scopes: ["write:statuses"]} when action not in @oauth_read_actions)
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
@doc "GET /api/v1/scheduled_statuses"

View file

@ -6,6 +6,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
use Pleroma.Web, :controller
alias Pleroma.Activity
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Plugs.RateLimiter
alias Pleroma.Repo
alias Pleroma.User
@ -15,6 +16,12 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
alias Pleroma.Web.MastodonAPI.StatusView
require Logger
# Note: Mastodon doesn't allow unauthenticated access (requires read:accounts / read:search)
plug(OAuthScopesPlug, %{scopes: ["read:search"], fallback: :proceed_unauthenticated})
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
plug(RateLimiter, :search when action in [:search, :search2, :account_search])
def account_search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do

View file

@ -5,13 +5,14 @@
defmodule Pleroma.Web.MastodonAPI.StatusController do
use Pleroma.Web, :controller
import Pleroma.Web.ControllerHelper, only: [try_render: 3]
import Pleroma.Web.ControllerHelper, only: [try_render: 3, add_link_headers: 2]
require Ecto.Query
alias Pleroma.Activity
alias Pleroma.Bookmark
alias Pleroma.Object
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Plugs.RateLimiter
alias Pleroma.Repo
alias Pleroma.ScheduledActivity
@ -22,6 +23,61 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.MastodonAPI.ScheduledActivityView
@unauthenticated_access %{fallback: :proceed_unauthenticated, scopes: []}
plug(
OAuthScopesPlug,
%{@unauthenticated_access | scopes: ["read:statuses"]}
when action in [
:index,
:show,
:card,
:context
]
)
plug(
OAuthScopesPlug,
%{scopes: ["write:statuses"]}
when action in [
:create,
:delete,
:reblog,
:unreblog
]
)
plug(OAuthScopesPlug, %{scopes: ["read:favourites"]} when action == :favourites)
plug(
OAuthScopesPlug,
%{scopes: ["write:favourites"]} when action in [:favourite, :unfavourite]
)
plug(
OAuthScopesPlug,
%{scopes: ["write:mutes"]} when action in [:mute_conversation, :unmute_conversation]
)
plug(
OAuthScopesPlug,
%{@unauthenticated_access | scopes: ["read:accounts"]}
when action in [:favourited_by, :reblogged_by]
)
plug(OAuthScopesPlug, %{scopes: ["write:accounts"]} when action in [:pin, :unpin])
# Note: scope not present in Mastodon: read:bookmarks
plug(OAuthScopesPlug, %{scopes: ["read:bookmarks"]} when action == :bookmarks)
# Note: scope not present in Mastodon: write:bookmarks
plug(
OAuthScopesPlug,
%{scopes: ["write:bookmarks"]} when action in [:bookmark, :unbookmark]
)
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
@rate_limited_status_actions ~w(reblog unreblog favourite unfavourite create delete)a
plug(
@ -283,4 +339,39 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
render(conn, "context.json", activity: activity, activities: activities, user: user)
end
end
@doc "GET /api/v1/favourites"
def favourites(%{assigns: %{user: user}} = conn, params) do
params =
params
|> Map.put("type", "Create")
|> Map.put("favorited_by", user.ap_id)
|> Map.put("blocking_user", user)
activities =
ActivityPub.fetch_activities([], params)
|> Enum.reverse()
conn
|> add_link_headers(activities)
|> render("index.json", activities: activities, for: user, as: :activity)
end
@doc "GET /api/v1/bookmarks"
def bookmarks(%{assigns: %{user: user}} = conn, params) do
user = User.get_cached_by_id(user.id)
bookmarks =
user.id
|> Bookmark.for_user_query()
|> Pleroma.Pagination.fetch_paginated(params)
activities =
bookmarks
|> Enum.map(fn b -> Map.put(b.activity, :bookmark, Map.delete(b, :activity)) end)
conn
|> add_link_headers(bookmarks)
|> render("index.json", %{activities: activities, for: user, as: :activity})
end
end

View file

@ -12,6 +12,10 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionController do
action_fallback(:errors)
plug(Pleroma.Plugs.OAuthScopesPlug, %{scopes: ["push"]})
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
# Creates PushSubscription
# POST /api/v1/push/subscription
#

View file

@ -8,11 +8,16 @@ defmodule Pleroma.Web.MastodonAPI.SuggestionController do
require Logger
alias Pleroma.Config
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User
alias Pleroma.Web.MediaProxy
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
plug(OAuthScopesPlug, %{scopes: ["read"]} when action == :index)
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
@doc "GET /api/v1/suggestions"
def index(%{assigns: %{user: user}} = conn, _) do
if Config.get([:suggestions, :enabled], false) do

View file

@ -9,8 +9,14 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
only: [add_link_headers: 2, add_link_headers: 3, truthy_param?: 1]
alias Pleroma.Pagination
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Web.ActivityPub.ActivityPub
plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action in [:home, :direct])
plug(OAuthScopesPlug, %{scopes: ["read:lists"]} when action == :list)
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
plug(:put_view, Pleroma.Web.MastodonAPI.StatusView)
# GET /api/v1/timelines/home

View file

@ -167,6 +167,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|> maybe_put_chat_token(user, opts[:for], opts)
|> maybe_put_activation_status(user, opts[:for])
|> maybe_put_follow_requests_count(user, opts[:for])
|> maybe_put_unread_conversation_count(user, opts[:for])
end
defp username_from_nickname(string) when is_binary(string) do
@ -248,6 +249,16 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
defp maybe_put_activation_status(data, _, _), do: data
defp maybe_put_unread_conversation_count(data, %User{id: user_id} = user, %User{id: user_id}) do
data
|> Kernel.put_in(
[:pleroma, :unread_conversation_count],
user.info.unread_conversation_count
)
end
defp maybe_put_unread_conversation_count(data, _, _), do: data
defp image_url(%{"url" => [%{"href" => href} | _]}), do: href
defp image_url(_), do: nil
end

View file

@ -25,40 +25,44 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
parent_activity = Activity.get_create_by_object_ap_id(activity.data["object"])
mastodon_type = Activity.mastodon_notification_type(activity)
response = %{
id: to_string(notification.id),
type: mastodon_type,
created_at: CommonAPI.Utils.to_masto_date(notification.inserted_at),
account: AccountView.render("show.json", %{user: actor, for: user}),
pleroma: %{
is_seen: notification.seen
with %{id: _} = account <- AccountView.render("show.json", %{user: actor, for: user}) do
response = %{
id: to_string(notification.id),
type: mastodon_type,
created_at: CommonAPI.Utils.to_masto_date(notification.inserted_at),
account: account,
pleroma: %{
is_seen: notification.seen
}
}
}
case mastodon_type do
"mention" ->
response
|> Map.merge(%{
status: StatusView.render("show.json", %{activity: activity, for: user})
})
case mastodon_type do
"mention" ->
response
|> Map.merge(%{
status: StatusView.render("show.json", %{activity: activity, for: user})
})
"favourite" ->
response
|> Map.merge(%{
status: StatusView.render("show.json", %{activity: parent_activity, for: user})
})
"favourite" ->
response
|> Map.merge(%{
status: StatusView.render("show.json", %{activity: parent_activity, for: user})
})
"reblog" ->
response
|> Map.merge(%{
status: StatusView.render("show.json", %{activity: parent_activity, for: user})
})
"reblog" ->
response
|> Map.merge(%{
status: StatusView.render("show.json", %{activity: parent_activity, for: user})
})
"follow" ->
response
"follow" ->
response
_ ->
nil
_ ->
nil
end
else
_ -> nil
end
end
end

View file

@ -0,0 +1,23 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Metadata.Providers.Feed do
alias Pleroma.Web.Endpoint
alias Pleroma.Web.Metadata.Providers.Provider
alias Pleroma.Web.Router.Helpers
@behaviour Provider
@impl Provider
def build_tags(%{user: user}) do
[
{:link,
[
rel: "alternate",
type: "application/atom+xml",
href: Helpers.feed_path(Endpoint, :feed, user.nickname) <> ".atom"
], []}
]
end
end

View file

@ -4,10 +4,15 @@
defmodule Pleroma.Web.MongooseIM.MongooseIMController do
use Pleroma.Web, :controller
alias Comeonin.Pbkdf2
alias Pleroma.Plugs.RateLimiter
alias Pleroma.Repo
alias Pleroma.User
plug(RateLimiter, :authentication when action in [:user_exists, :check_password])
plug(RateLimiter, {:authentication, params: ["user"]} when action == :check_password)
def user_exists(conn, %{"user" => username}) do
with %User{} <- Repo.get_by(User, nickname: username, local: true) do
conn

View file

@ -24,6 +24,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
plug(:fetch_session)
plug(:fetch_flash)
plug(Pleroma.Plugs.RateLimiter, :authentication when action == :create_authorization)
action_fallback(Pleroma.Web.OAuth.FallbackController)
@ -460,7 +461,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
end
# Special case: Local MastodonFE
defp redirect_uri(%Plug.Conn{} = conn, "."), do: mastodon_api_url(conn, :login)
defp redirect_uri(%Plug.Conn{} = conn, "."), do: auth_url(conn, :login)
defp redirect_uri(%Plug.Conn{}, redirect_uri), do: redirect_uri
@ -474,7 +475,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
defp validate_scopes(app, params) do
params
|> Scopes.fetch_scopes(app.scopes)
|> Scopes.validates(app.scopes)
|> Scopes.validate(app.scopes)
end
def default_redirect_uri(%App{} = app) do

View file

@ -8,7 +8,7 @@ defmodule Pleroma.Web.OAuth.Scopes do
"""
@doc """
Fetch scopes from requiest params.
Fetch scopes from request params.
Note: `scopes` is used by Mastodon supporting it but sticking to
OAuth's standard `scope` wherever we control it
@ -53,14 +53,14 @@ defmodule Pleroma.Web.OAuth.Scopes do
@doc """
Validates scopes.
"""
@spec validates(list() | nil, list()) ::
@spec validate(list() | nil, list()) ::
{:ok, list()} | {:error, :missing_scopes | :unsupported_scopes}
def validates([], _app_scopes), do: {:error, :missing_scopes}
def validates(nil, _app_scopes), do: {:error, :missing_scopes}
def validate([], _app_scopes), do: {:error, :missing_scopes}
def validate(nil, _app_scopes), do: {:error, :missing_scopes}
def validates(scopes, app_scopes) do
case scopes -- app_scopes do
[] -> {:ok, scopes}
def validate(scopes, app_scopes) do
case Pleroma.Plugs.OAuthScopesPlug.filter_descendants(scopes, app_scopes) do
^scopes -> {:ok, scopes}
_ -> {:error, :unsupported_scopes}
end
end

View file

@ -9,16 +9,13 @@ defmodule Pleroma.Web.OStatus.OStatusController do
alias Pleroma.Activity
alias Pleroma.Object
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.ActivityPubController
alias Pleroma.Web.ActivityPub.ObjectView
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.Endpoint
alias Pleroma.Web.Federator
alias Pleroma.Web.Metadata.PlayerView
alias Pleroma.Web.OStatus
alias Pleroma.Web.OStatus.ActivityRepresenter
alias Pleroma.Web.OStatus.FeedRepresenter
alias Pleroma.Web.Router
alias Pleroma.Web.XML
@ -31,49 +28,11 @@ defmodule Pleroma.Web.OStatus.OStatusController do
plug(
Pleroma.Plugs.SetFormatPlug
when action in [:feed_redirect, :object, :activity, :notice]
when action in [:object, :activity, :notice]
)
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
with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do
redirect(conn, external: OStatus.feed_path(user))
end
end
def feed(conn, %{"nickname" => nickname} = params) do
with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do
activities =
params
|> Map.take(["max_id"])
|> Map.merge(%{"whole_db" => true, "actor_id" => user.ap_id})
|> ActivityPub.fetch_public_activities()
|> Enum.reverse()
response =
user
|> FeedRepresenter.to_simple_form(activities, [user])
|> :xmerl.export_simple(:xmerl_xml)
|> to_string
conn
|> put_resp_content_type("application/atom+xml")
|> send_resp(200, response)
end
end
defp decode_or_retry(body) do
with {:ok, magic_key} <- Pleroma.Web.Salmon.fetch_magic_key(body),
{:ok, doc} <- Pleroma.Web.Salmon.decode_and_validate(magic_key, body) do

View file

@ -9,6 +9,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
only: [json_response: 3, add_link_headers: 2, assign_account_by_id: 2]
alias Ecto.Changeset
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Plugs.RateLimiter
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
@ -17,6 +18,30 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
require Pleroma.Constants
plug(
OAuthScopesPlug,
%{scopes: ["follow", "write:follows"]} when action in [:subscribe, :unsubscribe]
)
plug(
OAuthScopesPlug,
%{scopes: ["write:accounts"]}
# Note: the following actions are not permission-secured in Mastodon:
when action in [
:update_avatar,
:update_banner,
:update_background
]
)
plug(OAuthScopesPlug, %{scopes: ["read:favourites"]} when action == :favourites)
# An extra safety measure for possible actions not guarded by OAuth permissions specification
plug(
Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
when action != :confirmation_resend
)
plug(RateLimiter, :account_confirmation_resend when action == :confirmation_resend)
plug(:assign_account_by_id when action in [:favourites, :subscribe, :unsubscribe])
plug(:put_view, Pleroma.Web.MastodonAPI.AccountView)

View file

@ -1,8 +1,26 @@
defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do
use Pleroma.Web, :controller
alias Pleroma.Plugs.OAuthScopesPlug
require Logger
plug(
OAuthScopesPlug,
%{scopes: ["write"]}
when action in [
:create,
:delete,
:download_from,
:list_from,
:import_from_fs,
:update_file,
:update_metadata
]
)
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
def emoji_dir_path do
Path.join(
Pleroma.Config.get!([:instance, :static_dir]),

View file

@ -5,9 +5,15 @@
defmodule Pleroma.Web.PleromaAPI.MascotController do
use Pleroma.Web, :controller
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
plug(OAuthScopesPlug, %{scopes: ["read:accounts"]} when action == :show)
plug(OAuthScopesPlug, %{scopes: ["write:accounts"]} when action != :show)
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
@doc "GET /api/v1/pleroma/mascot"
def show(%{assigns: %{user: user}} = conn, _params) do
json(conn, User.get_mascot(user))

View file

@ -9,11 +9,26 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
alias Pleroma.Conversation.Participation
alias Pleroma.Notification
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.MastodonAPI.ConversationView
alias Pleroma.Web.MastodonAPI.NotificationView
alias Pleroma.Web.MastodonAPI.StatusView
plug(
OAuthScopesPlug,
%{scopes: ["read:statuses"]} when action in [:conversation, :conversation_statuses]
)
plug(
OAuthScopesPlug,
%{scopes: ["write:conversations"]} when action == :update_conversation
)
plug(OAuthScopesPlug, %{scopes: ["write:notifications"]} when action == :read_notification)
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
def conversation(%{assigns: %{user: user}} = conn, %{"id" => participation_id}) do
with %Participation{} = participation <- Participation.get(participation_id),
true <- user.id == participation.user_id do

View file

@ -7,11 +7,17 @@ defmodule Pleroma.Web.PleromaAPI.ScrobbleController do
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2, fetch_integer_param: 2]
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MastodonAPI.StatusView
plug(OAuthScopesPlug, %{scopes: ["read"]} when action == :user_scrobbles)
plug(OAuthScopesPlug, %{scopes: ["write"]} when action != :user_scrobbles)
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
def new_scrobble(%{assigns: %{user: user}} = conn, %{"title" => _} = params) do
params =
if !params["length"] do

View file

@ -87,31 +87,6 @@ defmodule Pleroma.Web.Router do
plug(Pleroma.Plugs.EnsureUserKeyPlug)
end
pipeline :oauth_read_or_public do
plug(Pleroma.Plugs.OAuthScopesPlug, %{
scopes: ["read"],
fallback: :proceed_unauthenticated
})
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
end
pipeline :oauth_read do
plug(Pleroma.Plugs.OAuthScopesPlug, %{scopes: ["read"]})
end
pipeline :oauth_write do
plug(Pleroma.Plugs.OAuthScopesPlug, %{scopes: ["write"]})
end
pipeline :oauth_follow do
plug(Pleroma.Plugs.OAuthScopesPlug, %{scopes: ["follow"]})
end
pipeline :oauth_push do
plug(Pleroma.Plugs.OAuthScopesPlug, %{scopes: ["push"]})
end
pipeline :well_known do
plug(:accepts, ["json", "jrd+json", "xml", "xrd+xml"])
end
@ -154,7 +129,7 @@ defmodule Pleroma.Web.Router do
end
scope "/api/pleroma/admin", Pleroma.Web.AdminAPI do
pipe_through([:admin_api, :oauth_write])
pipe_through(:admin_api)
post("/users/follow", AdminAPIController, :user_follow)
post("/users/unfollow", AdminAPIController, :user_unfollow)
@ -214,7 +189,7 @@ defmodule Pleroma.Web.Router do
scope "/api/pleroma/emoji", Pleroma.Web.PleromaAPI do
scope "/packs" do
# Modifying packs
pipe_through([:admin_api, :oauth_write])
pipe_through(:admin_api)
post("/import_from_fs", EmojiAPIController, :import_from_fs)
@ -239,31 +214,20 @@ defmodule Pleroma.Web.Router do
post("/main/ostatus", UtilController, :remote_subscribe)
get("/ostatus_subscribe", UtilController, :remote_follow)
scope [] do
pipe_through(:oauth_follow)
post("/ostatus_subscribe", UtilController, :do_remote_follow)
end
post("/ostatus_subscribe", UtilController, :do_remote_follow)
end
scope "/api/pleroma", Pleroma.Web.TwitterAPI do
pipe_through(:authenticated_api)
scope [] do
pipe_through(:oauth_write)
post("/change_email", UtilController, :change_email)
post("/change_password", UtilController, :change_password)
post("/delete_account", UtilController, :delete_account)
put("/notification_settings", UtilController, :update_notificaton_settings)
post("/disable_account", UtilController, :disable_account)
post("/change_email", UtilController, :change_email)
post("/change_password", UtilController, :change_password)
post("/delete_account", UtilController, :delete_account)
put("/notification_settings", UtilController, :update_notificaton_settings)
post("/disable_account", UtilController, :disable_account)
end
scope [] do
pipe_through(:oauth_follow)
post("/blocks_import", UtilController, :blocks_import)
post("/follow_import", UtilController, :follow_import)
end
post("/blocks_import", UtilController, :blocks_import)
post("/follow_import", UtilController, :follow_import)
end
scope "/oauth", Pleroma.Web.OAuth do
@ -290,14 +254,14 @@ defmodule Pleroma.Web.Router do
scope "/api/v1/pleroma", Pleroma.Web.PleromaAPI do
scope [] do
pipe_through(:authenticated_api)
pipe_through(:oauth_read)
get("/conversations/:id/statuses", PleromaAPIController, :conversation_statuses)
get("/conversations/:id", PleromaAPIController, :conversation)
end
scope [] do
pipe_through(:authenticated_api)
pipe_through(:oauth_write)
patch("/conversations/:id", PleromaAPIController, :update_conversation)
post("/notifications/read", PleromaAPIController, :read_notification)
@ -313,13 +277,11 @@ defmodule Pleroma.Web.Router do
scope [] do
pipe_through(:api)
pipe_through(:oauth_read_or_public)
get("/accounts/:id/favourites", AccountController, :favourites)
end
scope [] do
pipe_through(:authenticated_api)
pipe_through(:oauth_follow)
post("/accounts/:id/subscribe", AccountController, :subscribe)
post("/accounts/:id/unsubscribe", AccountController, :unsubscribe)
@ -329,131 +291,114 @@ defmodule Pleroma.Web.Router do
end
scope "/api/v1/pleroma", Pleroma.Web.PleromaAPI do
pipe_through([:api, :oauth_read_or_public])
pipe_through(:api)
get("/accounts/:id/scrobbles", ScrobbleController, :user_scrobbles)
end
scope "/api/v1", Pleroma.Web.MastodonAPI do
pipe_through(:authenticated_api)
scope [] do
pipe_through(:oauth_read)
get("/accounts/verify_credentials", AccountController, :verify_credentials)
get("/accounts/verify_credentials", AccountController, :verify_credentials)
get("/accounts/relationships", AccountController, :relationships)
get("/accounts/relationships", AccountController, :relationships)
get("/accounts/:id/lists", AccountController, :lists)
get("/accounts/:id/identity_proofs", MastodonAPIController, :empty_array)
get("/accounts/:id/lists", AccountController, :lists)
get("/accounts/:id/identity_proofs", MastodonAPIController, :empty_array)
get("/follow_requests", FollowRequestController, :index)
get("/blocks", AccountController, :blocks)
get("/mutes", AccountController, :mutes)
get("/follow_requests", FollowRequestController, :index)
get("/blocks", MastodonAPIController, :blocks)
get("/mutes", MastodonAPIController, :mutes)
get("/timelines/home", TimelineController, :home)
get("/timelines/direct", TimelineController, :direct)
get("/timelines/home", TimelineController, :home)
get("/timelines/direct", TimelineController, :direct)
get("/favourites", StatusController, :favourites)
get("/bookmarks", StatusController, :bookmarks)
get("/favourites", MastodonAPIController, :favourites)
get("/bookmarks", MastodonAPIController, :bookmarks)
get("/notifications", NotificationController, :index)
get("/notifications/:id", NotificationController, :show)
post("/notifications/clear", NotificationController, :clear)
post("/notifications/dismiss", NotificationController, :dismiss)
delete("/notifications/destroy_multiple", NotificationController, :destroy_multiple)
get("/notifications", NotificationController, :index)
get("/notifications/:id", NotificationController, :show)
post("/notifications/clear", NotificationController, :clear)
post("/notifications/dismiss", NotificationController, :dismiss)
delete("/notifications/destroy_multiple", NotificationController, :destroy_multiple)
get("/scheduled_statuses", ScheduledActivityController, :index)
get("/scheduled_statuses/:id", ScheduledActivityController, :show)
get("/scheduled_statuses", ScheduledActivityController, :index)
get("/scheduled_statuses/:id", ScheduledActivityController, :show)
get("/lists", ListController, :index)
get("/lists/:id", ListController, :show)
get("/lists/:id/accounts", ListController, :list_accounts)
get("/lists", ListController, :index)
get("/lists/:id", ListController, :show)
get("/lists/:id/accounts", ListController, :list_accounts)
get("/domain_blocks", DomainBlockController, :index)
get("/domain_blocks", DomainBlockController, :index)
get("/filters", FilterController, :index)
get("/filters", FilterController, :index)
get("/suggestions", SuggestionController, :index)
get("/suggestions", SuggestionController, :index)
get("/conversations", ConversationController, :index)
post("/conversations/:id/read", ConversationController, :read)
get("/conversations", ConversationController, :index)
post("/conversations/:id/read", ConversationController, :read)
get("/endorsements", AccountController, :endorsements)
get("/endorsements", MastodonAPIController, :empty_array)
end
patch("/accounts/update_credentials", AccountController, :update_credentials)
scope [] do
pipe_through(:oauth_write)
post("/statuses", StatusController, :create)
delete("/statuses/:id", StatusController, :delete)
patch("/accounts/update_credentials", AccountController, :update_credentials)
post("/statuses/:id/reblog", StatusController, :reblog)
post("/statuses/:id/unreblog", StatusController, :unreblog)
post("/statuses/:id/favourite", StatusController, :favourite)
post("/statuses/:id/unfavourite", StatusController, :unfavourite)
post("/statuses/:id/pin", StatusController, :pin)
post("/statuses/:id/unpin", StatusController, :unpin)
post("/statuses/:id/bookmark", StatusController, :bookmark)
post("/statuses/:id/unbookmark", StatusController, :unbookmark)
post("/statuses/:id/mute", StatusController, :mute_conversation)
post("/statuses/:id/unmute", StatusController, :unmute_conversation)
post("/statuses", StatusController, :create)
delete("/statuses/:id", StatusController, :delete)
put("/scheduled_statuses/:id", ScheduledActivityController, :update)
delete("/scheduled_statuses/:id", ScheduledActivityController, :delete)
post("/statuses/:id/reblog", StatusController, :reblog)
post("/statuses/:id/unreblog", StatusController, :unreblog)
post("/statuses/:id/favourite", StatusController, :favourite)
post("/statuses/:id/unfavourite", StatusController, :unfavourite)
post("/statuses/:id/pin", StatusController, :pin)
post("/statuses/:id/unpin", StatusController, :unpin)
post("/statuses/:id/bookmark", StatusController, :bookmark)
post("/statuses/:id/unbookmark", StatusController, :unbookmark)
post("/statuses/:id/mute", StatusController, :mute_conversation)
post("/statuses/:id/unmute", StatusController, :unmute_conversation)
post("/polls/:id/votes", PollController, :vote)
put("/scheduled_statuses/:id", ScheduledActivityController, :update)
delete("/scheduled_statuses/:id", ScheduledActivityController, :delete)
post("/media", MediaController, :create)
put("/media/:id", MediaController, :update)
post("/polls/:id/votes", PollController, :vote)
delete("/lists/:id", ListController, :delete)
post("/lists", ListController, :create)
put("/lists/:id", ListController, :update)
post("/media", MediaController, :create)
put("/media/:id", MediaController, :update)
post("/lists/:id/accounts", ListController, :add_to_list)
delete("/lists/:id/accounts", ListController, :remove_from_list)
delete("/lists/:id", ListController, :delete)
post("/lists", ListController, :create)
put("/lists/:id", ListController, :update)
post("/filters", FilterController, :create)
get("/filters/:id", FilterController, :show)
put("/filters/:id", FilterController, :update)
delete("/filters/:id", FilterController, :delete)
post("/lists/:id/accounts", ListController, :add_to_list)
delete("/lists/:id/accounts", ListController, :remove_from_list)
post("/reports", ReportController, :create)
post("/filters", FilterController, :create)
get("/filters/:id", FilterController, :show)
put("/filters/:id", FilterController, :update)
delete("/filters/:id", FilterController, :delete)
post("/follows", AccountController, :follows)
post("/accounts/:id/follow", AccountController, :follow)
post("/accounts/:id/unfollow", AccountController, :unfollow)
post("/accounts/:id/block", AccountController, :block)
post("/accounts/:id/unblock", AccountController, :unblock)
post("/accounts/:id/mute", AccountController, :mute)
post("/accounts/:id/unmute", AccountController, :unmute)
post("/reports", ReportController, :create)
end
post("/follow_requests/:id/authorize", FollowRequestController, :authorize)
post("/follow_requests/:id/reject", FollowRequestController, :reject)
scope [] do
pipe_through(:oauth_follow)
post("/domain_blocks", DomainBlockController, :create)
delete("/domain_blocks", DomainBlockController, :delete)
post("/follows", MastodonAPIController, :follows)
post("/accounts/:id/follow", AccountController, :follow)
post("/accounts/:id/unfollow", AccountController, :unfollow)
post("/accounts/:id/block", AccountController, :block)
post("/accounts/:id/unblock", AccountController, :unblock)
post("/accounts/:id/mute", AccountController, :mute)
post("/accounts/:id/unmute", AccountController, :unmute)
post("/follow_requests/:id/authorize", FollowRequestController, :authorize)
post("/follow_requests/:id/reject", FollowRequestController, :reject)
post("/domain_blocks", DomainBlockController, :create)
delete("/domain_blocks", DomainBlockController, :delete)
end
scope [] do
pipe_through(:oauth_push)
post("/push/subscription", SubscriptionController, :create)
get("/push/subscription", SubscriptionController, :get)
put("/push/subscription", SubscriptionController, :update)
delete("/push/subscription", SubscriptionController, :delete)
end
post("/push/subscription", SubscriptionController, :create)
get("/push/subscription", SubscriptionController, :get)
put("/push/subscription", SubscriptionController, :update)
delete("/push/subscription", SubscriptionController, :delete)
end
scope "/api/web", Pleroma.Web do
pipe_through([:authenticated_api, :oauth_write])
pipe_through(:authenticated_api)
put("/settings", MastoFEController, :put_settings)
end
@ -478,30 +423,26 @@ defmodule Pleroma.Web.Router do
get("/trends", MastodonAPIController, :empty_array)
scope [] do
pipe_through(:oauth_read_or_public)
get("/timelines/public", TimelineController, :public)
get("/timelines/tag/:tag", TimelineController, :hashtag)
get("/timelines/list/:list_id", TimelineController, :list)
get("/timelines/public", TimelineController, :public)
get("/timelines/tag/:tag", TimelineController, :hashtag)
get("/timelines/list/:list_id", TimelineController, :list)
get("/statuses", StatusController, :index)
get("/statuses/:id", StatusController, :show)
get("/statuses/:id/context", StatusController, :context)
get("/statuses", StatusController, :index)
get("/statuses/:id", StatusController, :show)
get("/statuses/:id/context", StatusController, :context)
get("/polls/:id", PollController, :show)
get("/polls/:id", PollController, :show)
get("/accounts/:id/statuses", AccountController, :statuses)
get("/accounts/:id/followers", AccountController, :followers)
get("/accounts/:id/following", AccountController, :following)
get("/accounts/:id", AccountController, :show)
get("/accounts/:id/statuses", AccountController, :statuses)
get("/accounts/:id/followers", AccountController, :followers)
get("/accounts/:id/following", AccountController, :following)
get("/accounts/:id", AccountController, :show)
get("/search", SearchController, :search)
end
get("/search", SearchController, :search)
end
scope "/api/v2", Pleroma.Web.MastodonAPI do
pipe_through([:api, :oauth_read_or_public])
pipe_through(:api)
get("/search", SearchController, :search2)
end
@ -532,11 +473,7 @@ defmodule Pleroma.Web.Router do
get("/oauth_tokens", TwitterAPI.Controller, :oauth_tokens)
delete("/oauth_tokens/:id", TwitterAPI.Controller, :revoke_token)
scope [] do
pipe_through(:oauth_read)
post("/qvitter/statuses/notifications/read", TwitterAPI.Controller, :notifications_read)
end
post("/qvitter/statuses/notifications/read", TwitterAPI.Controller, :notifications_read)
end
pipeline :ap_service_actor do
@ -559,8 +496,9 @@ defmodule Pleroma.Web.Router do
get("/activities/:uuid", OStatus.OStatusController, :activity)
get("/notice/:id", OStatus.OStatusController, :notice)
get("/notice/:id/embed_player", OStatus.OStatusController, :notice_player)
get("/users/:nickname/feed", OStatus.OStatusController, :feed)
get("/users/:nickname", OStatus.OStatusController, :feed_redirect)
get("/users/:nickname/feed", Feed.FeedController, :feed)
get("/users/:nickname", Feed.FeedController, :feed_redirect)
post("/users/:nickname/salmon", OStatus.OStatusController, :salmon_incoming)
post("/push/hub/:nickname", Websub.WebsubController, :websub_subscription_request)
@ -581,7 +519,6 @@ defmodule Pleroma.Web.Router do
pipe_through(:ostatus)
get("/users/:nickname/outbox", ActivityPubController, :outbox)
get("/objects/:uuid/likes", ActivityPubController, :object_likes)
end
pipeline :activitypub_client do
@ -601,23 +538,14 @@ defmodule Pleroma.Web.Router do
scope "/", Pleroma.Web.ActivityPub do
pipe_through([:activitypub_client])
scope [] do
pipe_through(:oauth_read)
get("/api/ap/whoami", ActivityPubController, :whoami)
get("/users/:nickname/inbox", ActivityPubController, :read_inbox)
end
get("/api/ap/whoami", ActivityPubController, :whoami)
get("/users/:nickname/inbox", ActivityPubController, :read_inbox)
scope [] do
pipe_through(:oauth_write)
post("/users/:nickname/outbox", ActivityPubController, :update_outbox)
post("/api/ap/upload_media", ActivityPubController, :upload_media)
end
post("/users/:nickname/outbox", ActivityPubController, :update_outbox)
post("/api/ap/upload_media", ActivityPubController, :upload_media)
scope [] do
pipe_through(:oauth_read_or_public)
get("/users/:nickname/followers", ActivityPubController, :followers)
get("/users/:nickname/following", ActivityPubController, :following)
end
get("/users/:nickname/followers", ActivityPubController, :followers)
get("/users/:nickname/following", ActivityPubController, :following)
end
scope "/", Pleroma.Web.ActivityPub do
@ -667,10 +595,7 @@ defmodule Pleroma.Web.Router do
post("/auth/password", MastodonAPI.AuthController, :password_reset)
scope [] do
pipe_through(:oauth_read)
get("/web/*path", MastoFEController, :index)
end
get("/web/*path", MastoFEController, :index)
end
pipeline :remote_media do

View file

@ -202,7 +202,7 @@ defmodule Pleroma.Web.Salmon do
@spec publish(User.t(), Pleroma.Activity.t()) :: none
def publish(user, activity)
def publish(%{info: %{keys: keys}} = user, %{data: %{"type" => type}} = activity)
def publish(%{keys: keys} = user, %{data: %{"type" => type}} = activity)
when type in @supported_activities do
feed = ActivityRepresenter.to_simple_form(activity, user, true)
@ -238,7 +238,7 @@ defmodule Pleroma.Web.Salmon do
def publish(%{id: id}, _), do: Logger.debug(fn -> "Keys missing for user #{id}" end)
def gather_webfinger_links(%User{} = user) do
{:ok, _private, public} = Keys.keys_from_pem(user.info.keys)
{:ok, _private, public} = Keys.keys_from_pem(user.keys)
magic_key = encode_key(public)
[

View file

@ -1,3 +1,7 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Streamer.Ping do
use GenServer
require Logger

View file

@ -1,3 +1,7 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Streamer.State do
use GenServer
require Logger

View file

@ -1,3 +1,7 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Streamer.StreamerSocket do
defstruct transport_pid: nil, user: nil

View file

@ -1,3 +1,7 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Streamer.Supervisor do
use Supervisor

View file

@ -1,3 +1,7 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Streamer.Worker do
use GenServer
@ -128,11 +132,14 @@ defmodule Pleroma.Web.Streamer.Worker do
blocks = user.info.blocks || []
mutes = user.info.mutes || []
reblog_mutes = user.info.muted_reblogs || []
recipient_blocks = MapSet.new(blocks ++ mutes)
recipients = MapSet.new(item.recipients)
domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.info.domain_blocks)
with parent when not is_nil(parent) <- Object.normalize(item),
true <- Enum.all?([blocks, mutes, reblog_mutes], &(item.actor not in &1)),
true <- Enum.all?([blocks, mutes], &(parent.data["actor"] not in &1)),
true <- MapSet.disjoint?(recipients, recipient_blocks),
%{host: item_host} <- URI.parse(item.actor),
%{host: parent_host} <- URI.parse(parent.data["actor"]),
false <- Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, item_host),
@ -194,11 +201,8 @@ defmodule Pleroma.Web.Streamer.Worker do
# Get the current user so we have up-to-date blocks etc.
if socket_user do
user = User.get_cached_by_ap_id(socket_user.ap_id)
blocks = user.info.blocks || []
mutes = user.info.mutes || []
with true <- Enum.all?([blocks, mutes], &(item.actor not in &1)),
true <- thread_containment(item, user) do
if should_send?(user, item) do
send(transport_pid, {:text, StreamerView.render("update.json", item, user)})
end
else

View file

@ -0,0 +1,48 @@
<entry>
<activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
<id><%= @data["id"] %></id>
<title><%= "New note by #{@user.nickname}" %></title>
<content type="html"><%= activity_content(@activity) %></content>
<published><%= @data["published"] %></published>
<updated><%= @data["published"] %></updated>
<ostatus:conversation ref="<%= activity_context(@activity) %>"><%= activity_context(@activity) %></ostatus:conversation>
<link ref="<%= activity_context(@activity) %>" rel="ostatus:conversation"/>
<%= if @data["summary"] do %>
<summary><%= @data["summary"] %></summary>
<% end %>
<%= if @activity.local do %>
<link type="application/atom+xml" href='<%= @data["id"] %>' rel="self"/>
<link type="text/html" href='<%= @data["id"] %>' rel="alternate"/>
<% else %>
<link type="text/html" href='<%= @data["external_url"] %>' rel="alternate"/>
<% end %>
<%= for tag <- @data["tag"] || [] do %>
<category term="<%= tag %>"></category>
<% end %>
<%= for attachment <- @data["attachment"] || [] do %>
<link rel="enclosure" href="<%= attachment_href(attachment) %>" type="<%= attachment_type(attachment) %>"/>
<% end %>
<%= if @data["inReplyTo"] do %>
<thr:in-reply-to ref='<%= @data["inReplyTo"] %>' href='<%= get_href(@data["inReplyTo"]) %>'/>
<% end %>
<%= for id <- @activity.recipients do %>
<%= if id == Pleroma.Constants.as_public() do %>
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
<% else %>
<%= unless Regex.match?(~r/^#{Pleroma.Web.base_url()}.+followers$/, id) do %>
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="<%= id %>"/>
<% end %>
<% end %>
<% end %>
<%= for {emoji, file} <- @data["emoji"] || %{} do %>
<link name="<%= emoji %>" rel="emoji" href="<%= file %>"/>
<% end %>
</entry>

View file

@ -0,0 +1,17 @@
<author>
<id><%= @user.ap_id %></id>
<activity:object>http://activitystrea.ms/schema/1.0/person</activity:object>
<uri><%= @user.ap_id %></uri>
<poco:preferredUsername><%= @user.nickname %></poco:preferredUsername>
<poco:displayName><%= @user.name %></poco:displayName>
<poco:note><%= escape(@user.bio) %></poco:note>
<summary><%= escape(@user.bio) %></summary>
<name><%= @user.nickname %></name>
<link rel="avatar" href="<%= User.avatar_url(@user) %>"/>
<%= if User.banner_url(@user) do %>
<link rel="header" href="<%= User.banner_url(@user) %>"/>
<% end %>
<%= if @user.local do %>
<ap_enabled>true</ap_enabled>
<% end %>
</author>

View file

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<feed
xmlns="http://www.w3.org/2005/Atom"
xmlns:thr="http://purl.org/syndication/thread/1.0"
xmlns:activity="http://activitystrea.ms/spec/1.0/"
xmlns:poco="http://portablecontacts.net/spec/1.0"
xmlns:ostatus="http://ostatus.org/schema/1.0">
<id><%= feed_url(@conn, :feed, @user.nickname) <> ".atom" %></id>
<title><%= @user.nickname <> "'s timeline" %></title>
<updated><%= most_recent_update(@activities, @user) %></updated>
<logo><%= logo(@user) %></logo>
<link rel="hub" href="<%= websub_url(@conn, :websub_subscription_request, @user.nickname) %>"/>
<link rel="salmon" href="<%= o_status_url(@conn, :salmon_incoming, @user.nickname) %>"/>
<link rel="self" href="<%= '#{feed_url(@conn, :feed, @user.nickname)}.atom' %>" type="application/atom+xml"/>
<%= render @view_module, "_author.xml", assigns %>
<%= if last_activity(@activities) do %>
<link rel="next" href="<%= '#{feed_url(@conn, :feed, @user.nickname)}.atom?max_id=#{last_activity(@activities).id}' %>" type="application/atom+xml"/>
<% end %>
<%= for activity <- @activities do %>
<%= render @view_module, "_activity.xml", Map.merge(assigns, %{activity: activity, data: activity_object_data(activity)}) %>
<% end %>
</feed>

View file

@ -13,11 +13,34 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
alias Pleroma.Healthcheck
alias Pleroma.Notification
alias Pleroma.Plugs.AuthenticationPlug
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User
alias Pleroma.Web
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.WebFinger
plug(
OAuthScopesPlug,
%{scopes: ["follow", "write:follows"]}
when action in [:do_remote_follow, :follow_import]
)
plug(OAuthScopesPlug, %{scopes: ["follow", "write:blocks"]} when action == :blocks_import)
plug(
OAuthScopesPlug,
%{scopes: ["write:accounts"]}
when action in [
:change_email,
:change_password,
:delete_account,
:update_notificaton_settings,
:disable_account
]
)
plug(OAuthScopesPlug, %{scopes: ["write:notifications"]} when action == :notifications_read)
plug(Pleroma.Plugs.SetFormatPlug when action in [:config, :version])
def help_test(conn, _params) do

View file

@ -6,12 +6,17 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
use Pleroma.Web, :controller
alias Pleroma.Notification
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User
alias Pleroma.Web.OAuth.Token
alias Pleroma.Web.TwitterAPI.TokenView
require Logger
plug(OAuthScopesPlug, %{scopes: ["write:notifications"]} when action == :notifications_read)
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
action_fallback(:errors)
def confirm_email(conn, %{"user_id" => uid, "token" => token}) do