Merge remote-tracking branch 'origin/develop' into conversations-import

This commit is contained in:
lain 2019-05-15 17:47:29 +02:00
commit f168a1cbdc
152 changed files with 3061 additions and 1212 deletions

View file

@ -137,7 +137,7 @@ defmodule Mix.Tasks.Pleroma.Emoji do
])
)
files = Tesla.get!(client(), files_url).body |> Poison.decode!()
files = Tesla.get!(client(), files_url).body |> Jason.decode!()
IO.puts(IO.ANSI.format(["Unpacking ", :bright, pack_name]))
@ -239,7 +239,7 @@ defmodule Mix.Tasks.Pleroma.Emoji do
emoji_map = Pleroma.Emoji.make_shortcode_to_file_map(tmp_pack_dir, exts)
File.write!(files_name, Poison.encode!(emoji_map, pretty: true))
File.write!(files_name, Jason.encode!(emoji_map, pretty: true))
IO.puts("""
@ -248,11 +248,11 @@ defmodule Mix.Tasks.Pleroma.Emoji do
""")
if File.exists?("index.json") do
existing_data = File.read!("index.json") |> Poison.decode!()
existing_data = File.read!("index.json") |> Jason.decode!()
File.write!(
"index.json",
Poison.encode!(
Jason.encode!(
Map.merge(
existing_data,
pack_json
@ -263,14 +263,14 @@ defmodule Mix.Tasks.Pleroma.Emoji do
IO.puts("index.json file has been update with the #{name} pack")
else
File.write!("index.json", Poison.encode!(pack_json, pretty: true))
File.write!("index.json", Jason.encode!(pack_json, pretty: true))
IO.puts("index.json has been created with the #{name} pack")
end
end
defp fetch_manifest(from) do
Poison.decode!(
Jason.decode!(
if String.starts_with?(from, "http") do
Tesla.get!(client(), from).body
else

View file

@ -138,7 +138,7 @@ defmodule Mix.Tasks.Pleroma.User do
bio: bio
}
changeset = User.register_changeset(%User{}, params, confirmed: true)
changeset = User.register_changeset(%User{}, params, need_confirmation: false)
{:ok, _user} = User.register(changeset)
Mix.shell().info("User #{nickname} created")

View file

@ -132,7 +132,10 @@ defmodule Pleroma.Activity do
end
def get_by_id(id) do
Repo.get(Activity, id)
Activity
|> where([a], a.id == ^id)
|> restrict_deactivated_users()
|> Repo.one()
end
def get_by_id_with_object(id) do
@ -200,6 +203,7 @@ defmodule Pleroma.Activity do
def get_create_by_object_ap_id(ap_id) when is_binary(ap_id) do
create_by_object_ap_id(ap_id)
|> restrict_deactivated_users()
|> Repo.one()
end
@ -287,8 +291,41 @@ defmodule Pleroma.Activity do
|> Repo.all()
end
def follow_requests_for_actor(%Pleroma.User{ap_id: ap_id}) do
from(
a in Activity,
where:
fragment(
"? ->> 'type' = 'Follow'",
a.data
),
where:
fragment(
"? ->> 'state' = 'pending'",
a.data
),
where:
fragment(
"coalesce((?)->'object'->>'id', (?)->>'object') = ?",
a.data,
a.data,
^ap_id
)
)
end
@spec query_by_actor(actor()) :: Ecto.Query.t()
def query_by_actor(actor) do
from(a in Activity, where: a.actor == ^actor)
end
def restrict_deactivated_users(query) do
from(activity in query,
where:
fragment(
"? not in (SELECT ap_id FROM users WHERE info->'deactivated' @> 'true')",
activity.actor
)
)
end
end

View file

@ -15,7 +15,7 @@ defmodule Pleroma.Captcha.Kocaptcha do
%{error: "Kocaptcha service unavailable"}
{:ok, res} ->
json_resp = Poison.decode!(res.body)
json_resp = Jason.decode!(res.body)
%{
type: :kocaptcha,

View file

@ -12,8 +12,12 @@ defmodule Pleroma.Config do
def get([key], default), do: get(key, default)
def get([parent_key | keys], default) do
Application.get_env(:pleroma, parent_key)
|> get_in(keys) || default
case :pleroma
|> Application.get_env(parent_key)
|> get_in(keys) do
nil -> default
any -> any
end
end
def get(key, default) do

View file

@ -5,15 +5,6 @@
defmodule Pleroma.Config.DeprecationWarnings do
require Logger
def check_frontend_config_mechanism do
if Pleroma.Config.get(:fe) do
Logger.warn("""
!!!DEPRECATION WARNING!!!
You are using the old configuration mechanism for the frontend. Please check config.md.
""")
end
end
def check_hellthread_threshold do
if Pleroma.Config.get([:mrf_hellthread, :threshold]) do
Logger.warn("""
@ -24,7 +15,6 @@ defmodule Pleroma.Config.DeprecationWarnings do
end
def warn do
check_frontend_config_mechanism()
check_hellthread_threshold()
end
end

View file

@ -47,8 +47,8 @@ defmodule Pleroma.Conversation do
"""
def create_or_bump_for(activity, opts \\ []) do
with true <- Pleroma.Web.ActivityPub.Visibility.is_direct?(activity),
object <- Pleroma.Object.normalize(activity),
"Create" <- activity.data["type"],
object <- Pleroma.Object.normalize(activity),
"Note" <- object.data["type"],
ap_id when is_binary(ap_id) and byte_size(ap_id) > 0 <- object.data["context"] do
{:ok, conversation} = create_for_ap_id(ap_id)

View file

@ -77,13 +77,13 @@ defmodule Pleroma.Gopher.Server.ProtocolHandler do
user = User.get_cached_by_ap_id(activity.data["actor"])
object = Object.normalize(activity)
like_count = object["like_count"] || 0
announcement_count = object["announcement_count"] || 0
like_count = object.data["like_count"] || 0
announcement_count = object.data["announcement_count"] || 0
link("Post ##{activity.id} by #{user.nickname}", "/notices/#{activity.id}") <>
info("#{like_count} likes, #{announcement_count} repeats") <>
"i\tfake\t(NULL)\t0\r\n" <>
info(HTML.strip_tags(String.replace(object["content"], "<br>", "\r")))
info(HTML.strip_tags(String.replace(object.data["content"], "<br>", "\r")))
end)
|> Enum.join("i\tfake\t(NULL)\t0\r\n")
end

View file

@ -33,6 +33,13 @@ defmodule Pleroma.Notification do
def for_user_query(user) do
Notification
|> where(user_id: ^user.id)
|> where(
[n, a],
fragment(
"? not in (SELECT ap_id FROM users WHERE info->'deactivated' @> 'true')",
a.actor
)
)
|> join(:inner, [n], activity in assoc(n, :activity))
|> join(:left, [n, a], object in Object,
on:

View file

@ -0,0 +1,31 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug do
import Plug.Conn
alias Pleroma.Config
alias Pleroma.User
def init(options) do
options
end
def call(conn, _) do
public? = Config.get!([:instance, :public])
case {public?, conn} do
{true, _} ->
conn
{false, %{assigns: %{user: %User{}}}} ->
conn
{false, _} ->
conn
|> put_resp_content_type("application/json")
|> send_resp(403, Jason.encode!(%{error: "This resource requires authentication."}))
|> halt
end
end
end

View file

@ -4,7 +4,6 @@
defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.HTTPSignatures
import Plug.Conn
require Logger

View file

@ -8,6 +8,7 @@ defmodule Pleroma.Plugs.OAuthPlug do
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.OAuth.App
alias Pleroma.Web.OAuth.Token
@realm_reg Regex.compile!("Bearer\:?\s+(.*)$", "i")
@ -22,18 +23,39 @@ defmodule Pleroma.Plugs.OAuthPlug do
|> assign(:token, token_record)
|> assign(:user, user)
else
_ -> conn
_ ->
# token found, but maybe only with app
with {:ok, app, token_record} <- fetch_app_and_token(access_token) do
conn
|> assign(:token, token_record)
|> assign(:app, app)
else
_ -> conn
end
end
end
def call(conn, _) do
with {:ok, token_str} <- fetch_token_str(conn),
{:ok, user, token_record} <- fetch_user_and_token(token_str) do
conn
|> assign(:token, token_record)
|> assign(:user, user)
else
_ -> conn
case fetch_token_str(conn) do
{:ok, token} ->
with {:ok, user, token_record} <- fetch_user_and_token(token) do
conn
|> assign(:token, token_record)
|> assign(:user, user)
else
_ ->
# token found, but maybe only with app
with {:ok, app, token_record} <- fetch_app_and_token(token) do
conn
|> assign(:token, token_record)
|> assign(:app, app)
else
_ -> conn
end
end
_ ->
conn
end
end
@ -54,6 +76,16 @@ defmodule Pleroma.Plugs.OAuthPlug do
end
end
@spec fetch_app_and_token(String.t()) :: {:ok, App.t(), Token.t()} | nil
defp fetch_app_and_token(token) do
query =
from(t in Token, where: t.token == ^token, join: app in assoc(t, :app), preload: [app: app])
with %Token{app: app} = token_record <- Repo.one(query) do
{:ok, app, token_record}
end
end
# Gets token from session by :oauth_token key
#
@spec fetch_token_from_session(Plug.Conn.t()) :: :no_token_found | {:ok, String.t()}

View file

@ -0,0 +1,36 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Plugs.RateLimitPlug do
import Phoenix.Controller, only: [json: 2]
import Plug.Conn
def init(opts), do: opts
def call(conn, opts) do
enabled? = Pleroma.Config.get([:app_account_creation, :enabled])
case check_rate(conn, Map.put(opts, :enabled, enabled?)) do
{:ok, _count} -> conn
{:error, _count} -> render_error(conn)
%Plug.Conn{} = conn -> conn
end
end
defp check_rate(conn, %{enabled: true} = opts) do
max_requests = opts[:max_requests]
bucket_name = conn.remote_ip |> Tuple.to_list() |> Enum.join(".")
ExRated.check_rate(bucket_name, opts[:interval] * 1000, max_requests)
end
defp check_rate(conn, _), do: conn
defp render_error(conn) do
conn
|> put_status(:forbidden)
|> json(%{error: "Rate limit exceeded."})
|> halt()
end
end

41
lib/pleroma/signature.ex Normal file
View file

@ -0,0 +1,41 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Signature do
@behaviour HTTPSignatures.Adapter
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.Salmon
alias Pleroma.Web.WebFinger
def fetch_public_key(conn) do
with actor_id <- Utils.get_ap_id(conn.params["actor"]),
{:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
{:ok, public_key}
else
e ->
{:error, e}
end
end
def refetch_public_key(conn) do
with actor_id <- Utils.get_ap_id(conn.params["actor"]),
{:ok, _user} <- ActivityPub.make_user_from_ap_id(actor_id),
{:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
{:ok, public_key}
else
e ->
{:error, e}
end
end
def sign(%User{} = user, headers) do
with {:ok, %{info: %{keys: keys}}} <- WebFinger.ensure_keys_present(user),
{:ok, private_key, _} <- Salmon.keys_from_pem(keys) do
HTTPSignatures.sign(private_key, user.ap_id <> "#main-key", headers)
end
end
end

View file

@ -34,7 +34,7 @@ defmodule Pleroma.Stats do
def update_stats do
peers =
from(
u in Pleroma.User,
u in User,
select: fragment("distinct split_part(?, '@', 2)", u.nickname),
where: u.local != ^true
)
@ -44,10 +44,13 @@ defmodule Pleroma.Stats do
domain_count = Enum.count(peers)
status_query =
from(u in User.local_user_query(), select: fragment("sum((?->>'note_count')::int)", u.info))
from(u in User.Query.build(%{local: true}),
select: fragment("sum((?->>'note_count')::int)", u.info)
)
status_count = Repo.one(status_query)
user_count = Repo.aggregate(User.active_local_user_query(), :count, :id)
user_count = Repo.aggregate(User.Query.build(%{local: true, active: true}), :count, :id)
Agent.update(__MODULE__, fn _ ->
{peers, %{domain_count: domain_count, status_count: status_count, user_count: user_count}}

View file

@ -14,7 +14,7 @@ defmodule Pleroma.Uploaders.Swift.Keystone do
def process_response_body(body) do
body
|> Poison.decode!()
|> Jason.decode!()
end
def get_token do
@ -38,7 +38,7 @@ defmodule Pleroma.Uploaders.Swift.Keystone do
end
def make_auth_body(username, password, tenant) do
Poison.encode!(%{
Jason.encode!(%{
:auth => %{
:passwordCredentials => %{
:username => username,

View file

@ -105,10 +105,8 @@ defmodule Pleroma.User do
def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
def user_info(%User{} = user) do
oneself = if user.local, do: 1, else: 0
%{
following_count: length(user.following) - oneself,
following_count: following_count(user),
note_count: user.info.note_count,
follower_count: user.info.follower_count,
locked: user.info.locked,
@ -117,6 +115,20 @@ defmodule Pleroma.User do
}
end
def restrict_deactivated(query) do
from(u in query,
where: not fragment("? \\? 'deactivated' AND ?->'deactivated' @> 'true'", u.info, u.info)
)
end
def following_count(%User{following: []}), do: 0
def following_count(%User{} = user) do
user
|> get_friends_query()
|> Repo.aggregate(:count, :id)
end
def remote_user_creation(params) do
params =
params
@ -204,14 +216,15 @@ defmodule Pleroma.User do
end
def register_changeset(struct, params \\ %{}, opts \\ []) do
confirmation_status =
if opts[:confirmed] || !Pleroma.Config.get([:instance, :account_activation_required]) do
:confirmed
need_confirmation? =
if is_nil(opts[:need_confirmation]) do
Pleroma.Config.get([:instance, :account_activation_required])
else
:unconfirmed
opts[:need_confirmation]
end
info_change = User.Info.confirmation_changeset(%User.Info{}, confirmation_status)
info_change =
User.Info.confirmation_changeset(%User.Info{}, need_confirmation: need_confirmation?)
changeset =
struct
@ -254,10 +267,7 @@ defmodule Pleroma.User do
candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
autofollowed_users =
from(u in User,
where: u.local == true,
where: u.nickname in ^candidates
)
User.Query.build(%{nickname: candidates, local: true, deactivated: false})
|> Repo.all()
follow_all(user, autofollowed_users)
@ -415,24 +425,6 @@ defmodule Pleroma.User do
Enum.member?(follower.following, followed.follower_address)
end
def follow_import(%User{} = follower, followed_identifiers)
when is_list(followed_identifiers) do
Enum.map(
followed_identifiers,
fn followed_identifier ->
with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
{:ok, follower} <- maybe_direct_follow(follower, followed),
{:ok, _} <- ActivityPub.follow(follower, followed) do
followed
else
err ->
Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
err
end
end
)
end
def locked?(%User{} = user) do
user.info.locked || false
end
@ -554,8 +546,7 @@ defmodule Pleroma.User do
with [_nick, _domain] <- String.split(nickname, "@"),
{:ok, user} <- fetch_by_nickname(nickname) do
if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do
# TODO turn into job
{:ok, _} = Task.start(__MODULE__, :fetch_initial_posts, [user])
fetch_initial_posts(user)
end
{:ok, user}
@ -566,29 +557,20 @@ defmodule Pleroma.User do
end
@doc "Fetch some posts when the user has just been federated with"
def fetch_initial_posts(user) do
pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
def fetch_initial_posts(user),
do: PleromaJobQueue.enqueue(:background, __MODULE__, [:fetch_initial_posts, user])
Enum.each(
# Insert all the posts in reverse order, so they're in the right order on the timeline
Enum.reverse(Utils.fetch_ordered_collection(user.info.source_data["outbox"], pages)),
&Pleroma.Web.Federator.incoming_ap_doc/1
)
end
def get_followers_query(%User{id: id, follower_address: follower_address}, nil) do
from(
u in User,
where: fragment("? <@ ?", ^[follower_address], u.following),
where: u.id != ^id
)
@spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
def get_followers_query(%User{} = user, nil) do
User.Query.build(%{followers: user, deactivated: false})
end
def get_followers_query(user, page) do
from(u in get_followers_query(user, nil))
|> paginate(page, 20)
|> User.Query.paginate(page, 20)
end
@spec get_followers_query(User.t()) :: Ecto.Query.t()
def get_followers_query(user), do: get_followers_query(user, nil)
def get_followers(user, page \\ nil) do
@ -603,19 +585,17 @@ defmodule Pleroma.User do
Repo.all(from(u in q, select: u.id))
end
def get_friends_query(%User{id: id, following: following}, nil) do
from(
u in User,
where: u.follower_address in ^following,
where: u.id != ^id
)
@spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
def get_friends_query(%User{} = user, nil) do
User.Query.build(%{friends: user, deactivated: false})
end
def get_friends_query(user, page) do
from(u in get_friends_query(user, nil))
|> paginate(page, 20)
|> User.Query.paginate(page, 20)
end
@spec get_friends_query(User.t()) :: Ecto.Query.t()
def get_friends_query(user), do: get_friends_query(user, nil)
def get_friends(user, page \\ nil) do
@ -630,33 +610,10 @@ defmodule Pleroma.User do
Repo.all(from(u in q, select: u.id))
end
def get_follow_requests_query(%User{} = user) do
from(
a in Activity,
where:
fragment(
"? ->> 'type' = 'Follow'",
a.data
),
where:
fragment(
"? ->> 'state' = 'pending'",
a.data
),
where:
fragment(
"coalesce((?)->'object'->>'id', (?)->>'object') = ?",
a.data,
a.data,
^user.ap_id
)
)
end
@spec get_follow_requests(User.t()) :: {:ok, [User.t()]}
def get_follow_requests(%User{} = user) do
users =
user
|> User.get_follow_requests_query()
Activity.follow_requests_for_actor(user)
|> join(:inner, [a], u in User, on: a.actor == u.ap_id)
|> where([a, u], not fragment("? @> ?", u.following, ^[user.follower_address]))
|> group_by([a, u], u.id)
@ -720,18 +677,15 @@ defmodule Pleroma.User do
info_cng = User.Info.set_note_count(user.info, note_count)
cng =
change(user)
|> put_embed(:info, info_cng)
update_and_set_cache(cng)
user
|> change()
|> put_embed(:info, info_cng)
|> update_and_set_cache()
end
def update_follower_count(%User{} = user) do
follower_count_query =
User
|> where([u], ^user.follower_address in u.following)
|> where([u], u.id != ^user.id)
User.Query.build(%{followers: user, deactivated: false})
|> select([u], %{count: count(u.id)})
User
@ -755,38 +709,19 @@ defmodule Pleroma.User do
end
end
def get_users_from_set_query(ap_ids, false) do
from(
u in User,
where: u.ap_id in ^ap_ids
)
end
def get_users_from_set_query(ap_ids, true) do
query = get_users_from_set_query(ap_ids, false)
from(
u in query,
where: u.local == true
)
end
@spec get_users_from_set([String.t()], boolean()) :: [User.t()]
def get_users_from_set(ap_ids, local_only \\ true) do
get_users_from_set_query(ap_ids, local_only)
criteria = %{ap_id: ap_ids, deactivated: false}
criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
User.Query.build(criteria)
|> Repo.all()
end
@spec get_recipients_from_activity(Activity.t()) :: [User.t()]
def get_recipients_from_activity(%Activity{recipients: to}) do
query =
from(
u in User,
where: u.ap_id in ^to,
or_where: fragment("? && ?", u.following, ^to)
)
query = from(u in query, where: u.local == true)
Repo.all(query)
User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
|> Repo.all()
end
def search(query, resolve \\ false, for_user \\ nil) do
@ -883,6 +818,7 @@ defmodule Pleroma.User do
^processed_query
)
)
|> restrict_deactivated()
end
defp trigram_search_subquery(term) do
@ -901,23 +837,7 @@ defmodule Pleroma.User do
},
where: fragment("trim(? || ' ' || coalesce(?, '')) % ?", u.nickname, u.name, ^term)
)
end
def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
Enum.map(
blocked_identifiers,
fn blocked_identifier ->
with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
{:ok, blocker} <- block(blocker, blocked),
{:ok, _} <- ActivityPub.block(blocker, blocked) do
blocked
else
err ->
Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
err
end
end
)
|> restrict_deactivated()
end
def mute(muter, %User{ap_id: ap_id}) do
@ -1048,14 +968,23 @@ defmodule Pleroma.User do
end
end
def muted_users(user),
do: Repo.all(from(u in User, where: u.ap_id in ^user.info.mutes))
@spec muted_users(User.t()) :: [User.t()]
def muted_users(user) do
User.Query.build(%{ap_id: user.info.mutes, deactivated: false})
|> Repo.all()
end
def blocked_users(user),
do: Repo.all(from(u in User, where: u.ap_id in ^user.info.blocks))
@spec blocked_users(User.t()) :: [User.t()]
def blocked_users(user) do
User.Query.build(%{ap_id: user.info.blocks, deactivated: false})
|> Repo.all()
end
def subscribers(user),
do: Repo.all(from(u in User, where: u.ap_id in ^user.info.subscribers))
@spec subscribers(User.t()) :: [User.t()]
def subscribers(user) do
User.Query.build(%{ap_id: user.info.subscribers, deactivated: false})
|> Repo.all()
end
def block_domain(user, domain) do
info_cng =
@ -1081,77 +1010,25 @@ defmodule Pleroma.User do
update_and_set_cache(cng)
end
def maybe_local_user_query(query, local) do
if local, do: local_user_query(query), else: query
end
def local_user_query(query \\ User) do
from(
u in query,
where: u.local == true,
where: not is_nil(u.nickname)
)
end
def maybe_external_user_query(query, external) do
if external, do: external_user_query(query), else: query
end
def external_user_query(query \\ User) do
from(
u in query,
where: u.local == false,
where: not is_nil(u.nickname)
)
end
def maybe_active_user_query(query, active) do
if active, do: active_user_query(query), else: query
end
def active_user_query(query \\ User) do
from(
u in query,
where: fragment("not (?->'deactivated' @> 'true')", u.info),
where: not is_nil(u.nickname)
)
end
def maybe_deactivated_user_query(query, deactivated) do
if deactivated, do: deactivated_user_query(query), else: query
end
def deactivated_user_query(query \\ User) do
from(
u in query,
where: fragment("(?->'deactivated' @> 'true')", u.info),
where: not is_nil(u.nickname)
)
end
def active_local_user_query do
from(
u in local_user_query(),
where: fragment("not (?->'deactivated' @> 'true')", u.info)
)
end
def moderator_user_query do
from(
u in User,
where: u.local == true,
where: fragment("?->'is_moderator' @> 'true'", u.info)
)
def deactivate_async(user, status \\ true) do
PleromaJobQueue.enqueue(:background, __MODULE__, [:deactivate_async, user, status])
end
def deactivate(%User{} = user, status \\ true) do
info_cng = User.Info.set_activation_status(user.info, status)
cng =
change(user)
|> put_embed(:info, info_cng)
with {:ok, friends} <- User.get_friends(user),
{:ok, followers} <- User.get_followers(user),
{:ok, user} <-
user
|> change()
|> put_embed(:info, info_cng)
|> update_and_set_cache() do
Enum.each(followers, &invalidate_cache(&1))
Enum.each(friends, &update_follower_count(&1))
update_and_set_cache(cng)
{:ok, user}
end
end
def update_notification_settings(%User{} = user, settings \\ %{}) do
@ -1182,6 +1059,75 @@ defmodule Pleroma.User do
delete_user_activities(user)
end
@spec perform(atom(), User.t()) :: {:ok, User.t()}
def perform(:fetch_initial_posts, %User{} = user) do
pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
Enum.each(
# Insert all the posts in reverse order, so they're in the right order on the timeline
Enum.reverse(Utils.fetch_ordered_collection(user.info.source_data["outbox"], pages)),
&Pleroma.Web.Federator.incoming_ap_doc/1
)
{:ok, user}
end
def perform(:deactivate_async, user, status), do: deactivate(user, status)
@spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
when is_list(blocked_identifiers) do
Enum.map(
blocked_identifiers,
fn blocked_identifier ->
with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
{:ok, blocker} <- block(blocker, blocked),
{:ok, _} <- ActivityPub.block(blocker, blocked) do
blocked
else
err ->
Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
err
end
end
)
end
@spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
def perform(:follow_import, %User{} = follower, followed_identifiers)
when is_list(followed_identifiers) do
Enum.map(
followed_identifiers,
fn followed_identifier ->
with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
{:ok, follower} <- maybe_direct_follow(follower, followed),
{:ok, _} <- ActivityPub.follow(follower, followed) do
followed
else
err ->
Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
err
end
end
)
end
def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers),
do:
PleromaJobQueue.enqueue(:background, __MODULE__, [
:blocks_import,
blocker,
blocked_identifiers
])
def follow_import(%User{} = follower, followed_identifiers) when is_list(followed_identifiers),
do:
PleromaJobQueue.enqueue(:background, __MODULE__, [
:follow_import,
follower,
followed_identifiers
])
def delete_user_activities(%User{ap_id: ap_id} = user) do
stream =
ap_id
@ -1235,8 +1181,8 @@ defmodule Pleroma.User do
resp = fetch_by_ap_id(ap_id)
if should_fetch_initial do
with {:ok, %User{} = user} = resp do
{:ok, _} = Task.start(__MODULE__, :fetch_initial_posts, [user])
with {:ok, %User{} = user} <- resp do
fetch_initial_posts(user)
end
end
@ -1306,7 +1252,7 @@ defmodule Pleroma.User do
def ap_enabled?(_), do: false
@doc "Gets or fetch a user by uri or nickname."
@spec get_or_fetch(String.t()) :: User.t()
@spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
@ -1423,22 +1369,12 @@ defmodule Pleroma.User do
}
end
@spec all_superusers() :: [User.t()]
def all_superusers do
from(
u in User,
where: u.local == true,
where: fragment("?->'is_admin' @> 'true' OR ?->'is_moderator' @> 'true'", u.info, u.info)
)
User.Query.build(%{super_users: true, local: true, deactivated: false})
|> Repo.all()
end
defp paginate(query, page, page_size) do
from(u in query,
limit: ^page_size,
offset: ^((page - 1) * page_size)
)
end
def showing_reblogs?(%User{} = user, %User{} = target) do
target.ap_id not in user.info.muted_reblogs
end

View file

@ -8,6 +8,8 @@ defmodule Pleroma.User.Info do
alias Pleroma.User.Info
@type t :: %__MODULE__{}
embedded_schema do
field(:banner, :map, default: %{})
field(:background, :map, default: %{})
@ -210,21 +212,23 @@ defmodule Pleroma.User.Info do
])
end
def confirmation_changeset(info, :confirmed) do
confirmation_changeset(info, %{
confirmation_pending: false,
confirmation_token: nil
})
end
@spec confirmation_changeset(Info.t(), keyword()) :: Ecto.Changerset.t()
def confirmation_changeset(info, opts) do
need_confirmation? = Keyword.get(opts, :need_confirmation)
def confirmation_changeset(info, :unconfirmed) do
confirmation_changeset(info, %{
confirmation_pending: true,
confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
})
end
params =
if need_confirmation? do
%{
confirmation_pending: true,
confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
}
else
%{
confirmation_pending: false,
confirmation_token: nil
}
end
def confirmation_changeset(info, params) do
cast(info, params, [:confirmation_pending, :confirmation_token])
end

154
lib/pleroma/user/query.ex Normal file
View file

@ -0,0 +1,154 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.User.Query do
@moduledoc """
User query builder module. Builds query from new query or another user query.
## Example:
query = Pleroma.User.Query(%{nickname: "nickname"})
another_query = Pleroma.User.Query.build(query, %{email: "email@example.com"})
Pleroma.Repo.all(query)
Pleroma.Repo.all(another_query)
Adding new rules:
- *ilike criteria*
- add field to @ilike_criteria list
- pass non empty string
- e.g. Pleroma.User.Query.build(%{nickname: "nickname"})
- *equal criteria*
- add field to @equal_criteria list
- pass non empty string
- e.g. Pleroma.User.Query.build(%{email: "email@example.com"})
- *contains criteria*
- add field to @containns_criteria list
- pass values list
- e.g. Pleroma.User.Query.build(%{ap_id: ["http://ap_id1", "http://ap_id2"]})
"""
import Ecto.Query
import Pleroma.Web.AdminAPI.Search, only: [not_empty_string: 1]
alias Pleroma.User
@type criteria ::
%{
query: String.t(),
tags: [String.t()],
name: String.t(),
email: String.t(),
local: boolean(),
external: boolean(),
active: boolean(),
deactivated: boolean(),
is_admin: boolean(),
is_moderator: boolean(),
super_users: boolean(),
followers: User.t(),
friends: User.t(),
recipients_from_activity: [String.t()],
nickname: [String.t()],
ap_id: [String.t()]
}
| %{}
@ilike_criteria [:nickname, :name, :query]
@equal_criteria [:email]
@role_criteria [:is_admin, :is_moderator]
@contains_criteria [:ap_id, :nickname]
@spec build(criteria()) :: Query.t()
def build(query \\ base_query(), criteria) do
prepare_query(query, criteria)
end
@spec paginate(Ecto.Query.t(), pos_integer(), pos_integer()) :: Ecto.Query.t()
def paginate(query, page, page_size) do
from(u in query,
limit: ^page_size,
offset: ^((page - 1) * page_size)
)
end
defp base_query do
from(u in User)
end
defp prepare_query(query, criteria) do
Enum.reduce(criteria, query, &compose_query/2)
end
defp compose_query({key, value}, query)
when key in @ilike_criteria and not_empty_string(value) do
# hack for :query key
key = if key == :query, do: :nickname, else: key
where(query, [u], ilike(field(u, ^key), ^"%#{value}%"))
end
defp compose_query({key, value}, query)
when key in @equal_criteria and not_empty_string(value) do
where(query, [u], ^[{key, value}])
end
defp compose_query({key, values}, query) when key in @contains_criteria and is_list(values) do
where(query, [u], field(u, ^key) in ^values)
end
defp compose_query({:tags, tags}, query) when is_list(tags) and length(tags) > 0 do
Enum.reduce(tags, query, &prepare_tag_criteria/2)
end
defp compose_query({key, _}, query) when key in @role_criteria do
where(query, [u], fragment("(?->? @> 'true')", u.info, ^to_string(key)))
end
defp compose_query({:super_users, _}, query) do
where(
query,
[u],
fragment("?->'is_admin' @> 'true' OR ?->'is_moderator' @> 'true'", u.info, u.info)
)
end
defp compose_query({:local, _}, query), do: location_query(query, true)
defp compose_query({:external, _}, query), do: location_query(query, false)
defp compose_query({:active, _}, query) do
where(query, [u], fragment("not (?->'deactivated' @> 'true')", u.info))
|> where([u], not is_nil(u.nickname))
end
defp compose_query({:deactivated, false}, query) do
User.restrict_deactivated(query)
end
defp compose_query({:deactivated, true}, query) do
where(query, [u], fragment("?->'deactivated' @> 'true'", u.info))
|> where([u], not is_nil(u.nickname))
end
defp compose_query({:followers, %User{id: id, follower_address: follower_address}}, query) do
where(query, [u], fragment("? <@ ?", ^[follower_address], u.following))
|> where([u], u.id != ^id)
end
defp compose_query({:friends, %User{id: id, following: following}}, query) do
where(query, [u], u.follower_address in ^following)
|> where([u], u.id != ^id)
end
defp compose_query({:recipients_from_activity, to}, query) do
where(query, [u], u.ap_id in ^to or fragment("? && ?", u.following, ^to))
end
defp compose_query(_unsupported_param, query), do: query
defp prepare_tag_criteria(tag, query) do
or_where(query, [u], fragment("? = any(?)", ^tag, u.tags))
end
defp location_query(query, local) do
where(query, [u], u.local == ^local)
|> where([u], not is_nil(u.nickname))
end
end

View file

@ -5,7 +5,6 @@
defmodule Pleroma.Web.ActivityPub.ActivityPub do
alias Pleroma.Activity
alias Pleroma.Conversation
alias Pleroma.Instances
alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.Object.Fetcher
@ -15,7 +14,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
alias Pleroma.User
alias Pleroma.Web.ActivityPub.MRF
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.Federator
alias Pleroma.Web.WebFinger
import Ecto.Query
@ -24,8 +22,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
require Logger
@httpoison Application.get_env(:pleroma, :httpoison)
# 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.
defp get_recipients(%{"type" => "Announce"} = data) do
@ -137,9 +133,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
activity
end
Task.start(fn ->
Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
end)
PleromaJobQueue.enqueue(:background, Pleroma.Web.RichMedia.Helpers, [:fetch, activity])
Notification.create_notifications(activity)
@ -848,6 +842,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|> restrict_reblogs(opts)
|> restrict_pinned(opts)
|> restrict_muted_reblogs(opts)
|> Activity.restrict_deactivated_users()
end
def fetch_activities(recipients, opts \\ %{}) do
@ -951,89 +946,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
end
def should_federate?(inbox, public) do
if public do
true
else
inbox_info = URI.parse(inbox)
!Enum.member?(Pleroma.Config.get([:instance, :quarantined_instances], []), inbox_info.host)
end
end
def publish(actor, activity) do
remote_followers =
if actor.follower_address in activity.recipients do
{:ok, followers} = User.get_followers(actor)
followers |> Enum.filter(&(!&1.local))
else
[]
end
public = is_public?(activity)
{:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
json = Jason.encode!(data)
(Pleroma.Web.Salmon.remote_users(activity) ++ remote_followers)
|> Enum.filter(fn user -> User.ap_enabled?(user) end)
|> Enum.map(fn %{info: %{source_data: data}} ->
(is_map(data["endpoints"]) && Map.get(data["endpoints"], "sharedInbox")) || data["inbox"]
end)
|> Enum.uniq()
|> Enum.filter(fn inbox -> should_federate?(inbox, public) end)
|> Instances.filter_reachable()
|> Enum.each(fn {inbox, unreachable_since} ->
Federator.publish_single_ap(%{
inbox: inbox,
json: json,
actor: actor,
id: activity.data["id"],
unreachable_since: unreachable_since
})
end)
end
def publish_one(%{inbox: inbox, json: json, actor: actor, id: id} = params) do
Logger.info("Federating #{id} to #{inbox}")
host = URI.parse(inbox).host
digest = "SHA-256=" <> (:crypto.hash(:sha256, json) |> Base.encode64())
date =
NaiveDateTime.utc_now()
|> Timex.format!("{WDshort}, {0D} {Mshort} {YYYY} {h24}:{m}:{s} GMT")
signature =
Pleroma.Web.HTTPSignatures.sign(actor, %{
host: host,
"content-length": byte_size(json),
digest: digest,
date: date
})
with {:ok, %{status: code}} when code in 200..299 <-
result =
@httpoison.post(
inbox,
json,
[
{"Content-Type", "application/activity+json"},
{"Date", date},
{"signature", signature},
{"digest", digest}
]
) do
if !Map.has_key?(params, :unreachable_since) || params[:unreachable_since],
do: Instances.set_reachable(inbox)
result
else
{_post_result, response} ->
unless params[:unreachable_since], do: Instances.set_unreachable(inbox)
{:error, response}
end
end
# filter out broken threads
def contain_broken_threads(%Activity{} = activity, %User{} = user) do
entire_thread_visible_for_user?(activity, user)

View file

@ -0,0 +1,152 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.Publisher do
alias Pleroma.Activity
alias Pleroma.Config
alias Pleroma.Instances
alias Pleroma.User
alias Pleroma.Web.ActivityPub.Relay
alias Pleroma.Web.ActivityPub.Transmogrifier
import Pleroma.Web.ActivityPub.Visibility
@behaviour Pleroma.Web.Federator.Publisher
require Logger
@httpoison Application.get_env(:pleroma, :httpoison)
@moduledoc """
ActivityPub outgoing federation module.
"""
@doc """
Determine if an activity can be represented by running it through Transmogrifier.
"""
def is_representable?(%Activity{} = activity) do
with {:ok, _data} <- Transmogrifier.prepare_outgoing(activity.data) do
true
else
_e ->
false
end
end
@doc """
Publish a single message to a peer. Takes a struct with the following
parameters set:
* `inbox`: the inbox to publish to
* `json`: the JSON message body representing the ActivityPub message
* `actor`: the actor which is signing the message
* `id`: the ActivityStreams URI of the message
"""
def publish_one(%{inbox: inbox, json: json, actor: %User{} = actor, id: id} = params) do
Logger.info("Federating #{id} to #{inbox}")
host = URI.parse(inbox).host
digest = "SHA-256=" <> (:crypto.hash(:sha256, json) |> Base.encode64())
date =
NaiveDateTime.utc_now()
|> Timex.format!("{WDshort}, {0D} {Mshort} {YYYY} {h24}:{m}:{s} GMT")
signature =
Pleroma.Signature.sign(actor, %{
host: host,
"content-length": byte_size(json),
digest: digest,
date: date
})
with {:ok, %{status: code}} when code in 200..299 <-
result =
@httpoison.post(
inbox,
json,
[
{"Content-Type", "application/activity+json"},
{"Date", date},
{"signature", signature},
{"digest", digest}
]
) do
if !Map.has_key?(params, :unreachable_since) || params[:unreachable_since],
do: Instances.set_reachable(inbox)
result
else
{_post_result, response} ->
unless params[:unreachable_since], do: Instances.set_unreachable(inbox)
{:error, response}
end
end
defp should_federate?(inbox, public) do
if public do
true
else
inbox_info = URI.parse(inbox)
!Enum.member?(Pleroma.Config.get([:instance, :quarantined_instances], []), inbox_info.host)
end
end
@doc """
Publishes an activity to all relevant peers.
"""
def publish(%User{} = actor, %Activity{} = activity) do
remote_followers =
if actor.follower_address in activity.recipients do
{:ok, followers} = User.get_followers(actor)
followers |> Enum.filter(&(!&1.local))
else
[]
end
public = is_public?(activity)
if public && Config.get([:instance, :allow_relay]) do
Logger.info(fn -> "Relaying #{activity.data["id"]} out" end)
Relay.publish(activity)
end
{:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
json = Jason.encode!(data)
(Pleroma.Web.Salmon.remote_users(activity) ++ remote_followers)
|> Enum.filter(fn user -> User.ap_enabled?(user) end)
|> Enum.map(fn %{info: %{source_data: data}} ->
(is_map(data["endpoints"]) && Map.get(data["endpoints"], "sharedInbox")) || data["inbox"]
end)
|> Enum.uniq()
|> Enum.filter(fn inbox -> should_federate?(inbox, public) end)
|> Instances.filter_reachable()
|> Enum.each(fn {inbox, unreachable_since} ->
Pleroma.Web.Federator.Publisher.enqueue_one(
__MODULE__,
%{
inbox: inbox,
json: json,
actor: actor,
id: activity.data["id"],
unreachable_since: unreachable_since
}
)
end)
end
def gather_webfinger_links(%User{} = user) do
[
%{"rel" => "self", "type" => "application/activity+json", "href" => user.ap_id},
%{
"rel" => "self",
"type" => "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"",
"href" => user.ap_id
}
]
end
def gather_nodeinfo_protocol_names, do: ["activitypub"]
end

View file

@ -682,7 +682,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
"""
def fetch_ordered_collection(from, pages_left, acc \\ []) do
with {:ok, response} <- Tesla.get(from),
{:ok, collection} <- Poison.decode(response.body) do
{:ok, collection} <- Jason.decode(response.body) do
case collection["type"] do
"OrderedCollection" ->
# If we've encountered the OrderedCollection and not the page,

View file

@ -59,4 +59,28 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
visible_for_user?(tail, user)
end
end
def get_visibility(object) do
public = "https://www.w3.org/ns/activitystreams#Public"
to = object.data["to"] || []
cc = object.data["cc"] || []
cond do
public in to ->
"public"
public in cc ->
"unlisted"
# this should use the sql for the object's activity
Enum.any?(to, &String.contains?(&1, "/followers")) ->
"private"
length(cc) > 0 ->
"private"
true ->
"direct"
end
end
end

View file

@ -59,7 +59,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
bio: "."
}
changeset = User.register_changeset(%User{}, user_data, confirmed: true)
changeset = User.register_changeset(%User{}, user_data, need_confirmation: false)
{:ok, user} = User.register(changeset)
conn
@ -101,7 +101,10 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
search_params = %{
query: params["query"],
page: page,
page_size: page_size
page_size: page_size,
tags: params["tags"],
name: params["name"],
email: params["email"]
}
with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)),
@ -116,11 +119,11 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
)
end
@filters ~w(local external active deactivated)
defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
@filters ~w(local external active deactivated is_admin is_moderator)
@spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
defp maybe_parse_filters(filters) do
filters
|> String.split(",")

View file

@ -10,45 +10,23 @@ defmodule Pleroma.Web.AdminAPI.Search do
@page_size 50
def user(%{query: term} = params) when is_nil(term) or term == "" do
query = maybe_filtered_query(params)
defmacro not_empty_string(string) do
quote do
is_binary(unquote(string)) and unquote(string) != ""
end
end
@spec user(map()) :: {:ok, [User.t()], pos_integer()}
def user(params \\ %{}) do
query = User.Query.build(params) |> order_by([u], u.nickname)
paginated_query =
maybe_filtered_query(params)
|> paginate(params[:page] || 1, params[:page_size] || @page_size)
User.Query.paginate(query, params[:page] || 1, params[:page_size] || @page_size)
count = query |> Repo.aggregate(:count, :id)
count = Repo.aggregate(query, :count, :id)
results = Repo.all(paginated_query)
{:ok, results, count}
end
def user(%{query: term} = params) when is_binary(term) do
search_query = from(u in maybe_filtered_query(params), where: ilike(u.nickname, ^"%#{term}%"))
count = search_query |> Repo.aggregate(:count, :id)
results =
search_query
|> paginate(params[:page] || 1, params[:page_size] || @page_size)
|> Repo.all()
{:ok, results, count}
end
defp maybe_filtered_query(params) do
from(u in User, order_by: u.nickname)
|> User.maybe_local_user_query(params[:local])
|> User.maybe_external_user_query(params[:external])
|> User.maybe_active_user_query(params[:active])
|> User.maybe_deactivated_user_query(params[:deactivated])
end
defp paginate(query, page, page_size) do
from(u in query,
limit: ^page_size,
offset: ^((page - 1) * page_size)
)
end
end

View file

@ -74,7 +74,7 @@ defmodule Pleroma.Web.Auth.PleromaAuthenticator do
password_confirmation: random_password
},
external: true,
confirmed: true
need_confirmation: false
)
|> Repo.insert(),
{:ok, _} <-

View file

@ -116,32 +116,34 @@ defmodule Pleroma.Web.CommonAPI do
end
end
def get_visibility(%{"visibility" => visibility})
def get_visibility(%{"visibility" => visibility}, in_reply_to)
when visibility in ~w{public unlisted private direct},
do: visibility
do: {visibility, get_replied_to_visibility(in_reply_to)}
def get_visibility(%{"in_reply_to_status_id" => status_id}) when not is_nil(status_id) do
case get_replied_to_activity(status_id) do
nil ->
"public"
def get_visibility(_, in_reply_to) when not is_nil(in_reply_to) do
visibility = get_replied_to_visibility(in_reply_to)
{visibility, visibility}
end
in_reply_to ->
# XXX: these heuristics should be moved out of MastodonAPI.
with %Object{} = object <- Object.normalize(in_reply_to) do
Pleroma.Web.MastodonAPI.StatusView.get_visibility(object)
end
def get_visibility(_, in_reply_to), do: {"public", get_replied_to_visibility(in_reply_to)}
def get_replied_to_visibility(nil), do: nil
def get_replied_to_visibility(activity) do
with %Object{} = object <- Object.normalize(activity) do
Pleroma.Web.ActivityPub.Visibility.get_visibility(object)
end
end
def get_visibility(_), do: "public"
def post(user, %{"status" => status} = data) do
visibility = get_visibility(data)
limit = Pleroma.Config.get([:instance, :limit])
with status <- String.trim(status),
attachments <- attachments_from_ids(data),
in_reply_to <- get_replied_to_activity(data["in_reply_to_status_id"]),
{visibility, in_reply_to_visibility} <- get_visibility(data, in_reply_to),
{_, false} <-
{:private_to_public, in_reply_to_visibility == "direct" && visibility != "direct"},
{content_html, mentions, tags} <-
make_content_html(
status,
@ -185,6 +187,8 @@ defmodule Pleroma.Web.CommonAPI do
)
res
else
e -> {:error, e}
end
end

View file

@ -29,6 +29,13 @@ defmodule Pleroma.Web.Endpoint do
# credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength
)
plug(Plug.Static.IndexHtml, at: "/pleroma/admin/")
plug(Plug.Static,
at: "/pleroma/admin/",
from: {:pleroma, "priv/static/adminfe/"}
)
# Code reloading can be explicitly enabled under the
# :code_reloader configuration of your endpoint.
if code_reloading? do

View file

@ -7,13 +7,10 @@ defmodule Pleroma.Web.Federator do
alias Pleroma.Object.Containment
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Relay
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.Federator.Publisher
alias Pleroma.Web.Federator.RetryQueue
alias Pleroma.Web.OStatus
alias Pleroma.Web.Salmon
alias Pleroma.Web.WebFinger
alias Pleroma.Web.Websub
@ -42,14 +39,6 @@ defmodule Pleroma.Web.Federator do
PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:publish, activity], priority)
end
def publish_single_ap(params) do
PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:publish_single_ap, params])
end
def publish_single_websub(websub) do
PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:publish_single_websub, websub])
end
def verify_websub(websub) do
PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:verify_websub, websub])
end
@ -62,10 +51,6 @@ defmodule Pleroma.Web.Federator do
PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:refresh_subscriptions])
end
def publish_single_salmon(params) do
PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:publish_single_salmon, params])
end
# Job Worker Callbacks
def perform(:refresh_subscriptions) do
@ -95,23 +80,7 @@ defmodule Pleroma.Web.Federator do
with actor when not is_nil(actor) <- User.get_cached_by_ap_id(activity.data["actor"]) do
{:ok, actor} = WebFinger.ensure_keys_present(actor)
if Visibility.is_public?(activity) do
if OStatus.is_representable?(activity) do
Logger.info(fn -> "Sending #{activity.data["id"]} out via WebSub" end)
Websub.publish(Pleroma.Web.OStatus.feed_path(actor), actor, activity)
Logger.info(fn -> "Sending #{activity.data["id"]} out via Salmon" end)
Pleroma.Web.Salmon.publish(actor, activity)
end
if Keyword.get(Application.get_env(:pleroma, :instance), :allow_relay) do
Logger.info(fn -> "Relaying #{activity.data["id"]} out" end)
Relay.publish(activity)
end
end
Logger.info(fn -> "Sending #{activity.data["id"]} out via AP" end)
Pleroma.Web.ActivityPub.ActivityPub.publish(actor, activity)
Publisher.publish(actor, activity)
end
end
@ -148,25 +117,11 @@ defmodule Pleroma.Web.Federator do
_e ->
# Just drop those for now
Logger.info("Unhandled activity")
Logger.info(Poison.encode!(params, pretty: 2))
Logger.info(Jason.encode!(params, pretty: true))
:error
end
end
def perform(:publish_single_salmon, params) do
Salmon.send_to_user(params)
end
def perform(:publish_single_ap, params) do
case ActivityPub.publish_one(params) do
{:ok, _} ->
:ok
{:error, _} ->
RetryQueue.enqueue(params, ActivityPub)
end
end
def perform(
:publish_single_websub,
%{xml: _xml, topic: _topic, callback: _callback, secret: _secret} = params

View file

@ -0,0 +1,95 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Federator.Publisher do
alias Pleroma.Activity
alias Pleroma.Config
alias Pleroma.User
alias Pleroma.Web.Federator.RetryQueue
require Logger
@moduledoc """
Defines the contract used by federation implementations to publish messages to
their peers.
"""
@doc """
Determine whether an activity can be relayed using the federation module.
"""
@callback is_representable?(Pleroma.Activity.t()) :: boolean()
@doc """
Relays an activity to a specified peer, determined by the parameters. The
parameters used are controlled by the federation module.
"""
@callback publish_one(Map.t()) :: {:ok, Map.t()} | {:error, any()}
@doc """
Enqueue publishing a single activity.
"""
@spec enqueue_one(module(), Map.t()) :: :ok
def enqueue_one(module, %{} = params),
do: PleromaJobQueue.enqueue(:federation_outgoing, __MODULE__, [:publish_one, module, params])
@spec perform(atom(), module(), any()) :: {:ok, any()} | {:error, any()}
def perform(:publish_one, module, params) do
case apply(module, :publish_one, [params]) do
{:ok, _} ->
:ok
{:error, _e} ->
RetryQueue.enqueue(params, module)
end
end
def perform(type, _, _) do
Logger.debug("Unknown task: #{type}")
{:error, "Don't know what to do with this"}
end
@doc """
Relays an activity to all specified peers.
"""
@callback publish(Pleroma.User.t(), Pleroma.Activity.t()) :: :ok | {:error, any()}
@spec publish(Pleroma.User.t(), Pleroma.Activity.t()) :: :ok
def publish(%User{} = user, %Activity{} = activity) do
Config.get([:instance, :federation_publisher_modules])
|> Enum.each(fn module ->
if module.is_representable?(activity) do
Logger.info("Publishing #{activity.data["id"]} using #{inspect(module)}")
module.publish(user, activity)
end
end)
:ok
end
@doc """
Gathers links used by an outgoing federation module for WebFinger output.
"""
@callback gather_webfinger_links(Pleroma.User.t()) :: list()
@spec gather_webfinger_links(Pleroma.User.t()) :: list()
def gather_webfinger_links(%User{} = user) do
Config.get([:instance, :federation_publisher_modules])
|> Enum.reduce([], fn module, links ->
links ++ module.gather_webfinger_links(user)
end)
end
@doc """
Gathers nodeinfo protocol names supported by the federation module.
"""
@callback gather_nodeinfo_protocol_names() :: list()
@spec gather_nodeinfo_protocol_names() :: list()
def gather_nodeinfo_protocol_names do
Config.get([:instance, :federation_publisher_modules])
|> Enum.reduce([], fn module, links ->
links ++ module.gather_nodeinfo_protocol_names()
end)
end
end

View file

@ -1,91 +0,0 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
# https://tools.ietf.org/html/draft-cavage-http-signatures-08
defmodule Pleroma.Web.HTTPSignatures do
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Utils
require Logger
def split_signature(sig) do
default = %{"headers" => "date"}
sig =
sig
|> String.trim()
|> String.split(",")
|> Enum.reduce(default, fn part, acc ->
[key | rest] = String.split(part, "=")
value = Enum.join(rest, "=")
Map.put(acc, key, String.trim(value, "\""))
end)
Map.put(sig, "headers", String.split(sig["headers"], ~r/\s/))
end
def validate(headers, signature, public_key) do
sigstring = build_signing_string(headers, signature["headers"])
Logger.debug("Signature: #{signature["signature"]}")
Logger.debug("Sigstring: #{sigstring}")
{:ok, sig} = Base.decode64(signature["signature"])
:public_key.verify(sigstring, :sha256, sig, public_key)
end
def validate_conn(conn) do
# TODO: How to get the right key and see if it is actually valid for that request.
# For now, fetch the key for the actor.
with actor_id <- Utils.get_ap_id(conn.params["actor"]),
{:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
if validate_conn(conn, public_key) do
true
else
Logger.debug("Could not validate, re-fetching user and trying one more time")
# Fetch user anew and try one more time
with actor_id <- Utils.get_ap_id(conn.params["actor"]),
{:ok, _user} <- ActivityPub.make_user_from_ap_id(actor_id),
{:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
validate_conn(conn, public_key)
end
end
else
_e ->
Logger.debug("Could not public key!")
false
end
end
def validate_conn(conn, public_key) do
headers = Enum.into(conn.req_headers, %{})
signature = split_signature(headers["signature"])
validate(headers, signature, public_key)
end
def build_signing_string(headers, used_headers) do
used_headers
|> Enum.map(fn header -> "#{header}: #{headers[header]}" end)
|> Enum.join("\n")
end
def sign(user, headers) do
with {:ok, %{info: %{keys: keys}}} <- Pleroma.Web.WebFinger.ensure_keys_present(user),
{:ok, private_key, _} = Pleroma.Web.Salmon.keys_from_pem(keys) do
sigstring = build_signing_string(headers, Map.keys(headers))
signature =
:public_key.sign(sigstring, :sha256, private_key)
|> Base.encode64()
[
keyId: user.ap_id <> "#main-key",
algorithm: "rsa-sha256",
headers: Map.keys(headers) |> Enum.join(" "),
signature: signature
]
|> Enum.map(fn {k, v} -> "#{k}=\"#{v}\"" end)
|> Enum.join(",")
end
end
end

View file

@ -39,12 +39,22 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
alias Pleroma.Web.OAuth.Authorization
alias Pleroma.Web.OAuth.Scopes
alias Pleroma.Web.OAuth.Token
alias Pleroma.Web.TwitterAPI.TwitterAPI
alias Pleroma.Web.ControllerHelper
import Ecto.Query
require Logger
plug(
Pleroma.Plugs.RateLimitPlug,
%{
max_requests: Config.get([:app_account_creation, :max_requests]),
interval: Config.get([:app_account_creation, :interval])
}
when action in [:account_register]
)
@httpoison Application.get_env(:pleroma, :httpoison)
@local_mastodon_name "Mastodon-Local"
@ -168,7 +178,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end
end
@mastodon_api_level "2.6.5"
@mastodon_api_level "2.7.2"
def masto_instance(conn, _params) do
instance = Config.get(:instance)
@ -1536,7 +1546,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
user_id: user.id,
phrase: phrase,
context: context,
hide: Map.get(params, "irreversible", nil),
hide: Map.get(params, "irreversible", false),
whole_word: Map.get(params, "boolean", true)
# expires_at
}
@ -1693,6 +1703,53 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end
end
def account_register(
%{assigns: %{app: app}} = conn,
%{"username" => nickname, "email" => _, "password" => _, "agreement" => true} = params
) do
params =
params
|> Map.take([
"email",
"captcha_solution",
"captcha_token",
"captcha_answer_data",
"token",
"password"
])
|> Map.put("nickname", nickname)
|> Map.put("fullname", params["fullname"] || nickname)
|> Map.put("bio", params["bio"] || "")
|> Map.put("confirm", params["password"])
with {:ok, user} <- TwitterAPI.register_user(params, need_confirmation: true),
{:ok, token} <- Token.create_token(app, user, %{scopes: app.scopes}) do
json(conn, %{
token_type: "Bearer",
access_token: token.token,
scope: app.scopes,
created_at: Token.Utils.format_created_at(token)
})
else
{:error, errors} ->
conn
|> put_status(400)
|> json(Jason.encode!(errors))
end
end
def account_register(%{assigns: %{app: _app}} = conn, _params) do
conn
|> put_status(400)
|> json(%{error: "Missing parameters"})
end
def account_register(conn, _) do
conn
|> put_status(403)
|> json(%{error: "Invalid credentials"})
end
def conversations(%{assigns: %{user: user}} = conn, params) do
participations = Participation.for_user_with_last_activity_id(user, params)

View file

@ -16,6 +16,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
alias Pleroma.Web.MastodonAPI.StatusView
alias Pleroma.Web.MediaProxy
import Pleroma.Web.ActivityPub.Visibility, only: [get_visibility: 1]
# TODO: Add cached version.
defp get_replied_to_activities(activities) do
activities
@ -340,30 +342,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
end
end
def get_visibility(object) do
public = "https://www.w3.org/ns/activitystreams#Public"
to = object.data["to"] || []
cc = object.data["cc"] || []
cond do
public in to ->
"public"
public in cc ->
"unlisted"
# this should use the sql for the object's activity
Enum.any?(to, &String.contains?(&1, "/followers")) ->
"private"
length(cc) > 0 ->
"private"
true ->
"direct"
end
end
def render_content(%{data: %{"type" => "Video"}} = object) do
with name when not is_nil(name) and name != "" <- object.data["name"] do
"<p><a href=\"#{object.data["id"]}\">#{name}</a></p>#{object.data["content"]}"

View file

@ -10,6 +10,7 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
alias Pleroma.User
alias Pleroma.Web
alias Pleroma.Web.ActivityPub.MRF
alias Pleroma.Web.Federator.Publisher
plug(Pleroma.Web.FederatingPlug)
@ -137,7 +138,7 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
name: Pleroma.Application.name() |> String.downcase(),
version: Pleroma.Application.version()
},
protocols: ["ostatus", "activitypub"],
protocols: Publisher.gather_nodeinfo_protocol_names(),
services: %{
inbound: [],
outbound: []

View file

@ -7,6 +7,7 @@ defmodule Pleroma.Web.OAuth.App do
import Ecto.Changeset
@type t :: %__MODULE__{}
schema "apps" do
field(:client_name, :string)
field(:redirect_uris, :string)

View file

@ -14,6 +14,7 @@ defmodule Pleroma.Web.OAuth.Authorization do
import Ecto.Query
@type t :: %__MODULE__{}
schema "oauth_authorizations" do
field(:token, :string)
field(:scopes, {:array, :string}, default: [])
@ -25,28 +26,45 @@ defmodule Pleroma.Web.OAuth.Authorization do
timestamps()
end
@spec create_authorization(App.t(), User.t() | %{}, [String.t()] | nil) ::
{:ok, Authorization.t()} | {:error, Changeset.t()}
def create_authorization(%App{} = app, %User{} = user, scopes \\ nil) do
scopes = scopes || app.scopes
token = :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false)
authorization = %Authorization{
token: token,
used: false,
%{
scopes: scopes || app.scopes,
user_id: user.id,
app_id: app.id,
scopes: scopes,
valid_until: NaiveDateTime.add(NaiveDateTime.utc_now(), 60 * 10)
app_id: app.id
}
Repo.insert(authorization)
|> create_changeset()
|> Repo.insert()
end
@spec create_changeset(map()) :: Changeset.t()
def create_changeset(attrs \\ %{}) do
%Authorization{}
|> cast(attrs, [:user_id, :app_id, :scopes, :valid_until])
|> validate_required([:app_id, :scopes])
|> add_token()
|> add_lifetime()
end
defp add_token(changeset) do
token = :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false)
put_change(changeset, :token, token)
end
defp add_lifetime(changeset) do
put_change(changeset, :valid_until, NaiveDateTime.add(NaiveDateTime.utc_now(), 60 * 10))
end
@spec use_changeset(Authtorizatiton.t(), map()) :: Changeset.t()
def use_changeset(%Authorization{} = auth, params) do
auth
|> cast(params, [:used])
|> validate_required([:used])
end
@spec use_token(Authorization.t()) ::
{:ok, Authorization.t()} | {:error, Changeset.t()} | {:error, String.t()}
def use_token(%Authorization{used: false, valid_until: valid_until} = auth) do
if NaiveDateTime.diff(NaiveDateTime.utc_now(), valid_until) < 0 do
Repo.update(use_changeset(auth, %{used: true}))
@ -57,6 +75,7 @@ defmodule Pleroma.Web.OAuth.Authorization do
def use_token(%Authorization{used: true}), do: {:error, "already used"}
@spec delete_user_authorizations(User.t()) :: {integer(), any()}
def delete_user_authorizations(%User{id: user_id}) do
from(
a in Pleroma.Web.OAuth.Authorization,

View file

@ -19,8 +19,6 @@ defmodule Pleroma.Web.OAuth.OAuthController do
if Pleroma.Config.oauth_consumer_enabled?(), do: plug(Ueberauth)
@expires_in Pleroma.Config.get([:oauth2, :token_expires_in], 600)
plug(:fetch_session)
plug(:fetch_flash)
@ -144,14 +142,14 @@ defmodule Pleroma.Web.OAuth.OAuthController do
@doc "Renew access_token with refresh_token"
def token_exchange(
conn,
%{"grant_type" => "refresh_token", "refresh_token" => token} = params
%{"grant_type" => "refresh_token", "refresh_token" => token} = _params
) do
with %App{} = app <- get_app_from_request(conn, params),
with {:ok, app} <- Token.Utils.fetch_app(conn),
{:ok, %{user: user} = token} <- Token.get_by_refresh_token(app, token),
{:ok, token} <- RefreshToken.grant(token) do
response_attrs = %{created_at: Token.Utils.format_created_at(token)}
json(conn, response_token(user, token, response_attrs))
json(conn, Token.Response.build(user, token, response_attrs))
else
_error ->
put_status(conn, 400)
@ -160,14 +158,14 @@ defmodule Pleroma.Web.OAuth.OAuthController do
end
def token_exchange(conn, %{"grant_type" => "authorization_code"} = params) do
with %App{} = app <- get_app_from_request(conn, params),
with {:ok, app} <- Token.Utils.fetch_app(conn),
fixed_token = Token.Utils.fix_padding(params["code"]),
{:ok, auth} <- Authorization.get_by_token(app, fixed_token),
%User{} = user <- User.get_cached_by_id(auth.user_id),
{:ok, token} <- Token.exchange_token(app, auth) do
response_attrs = %{created_at: Token.Utils.format_created_at(token)}
json(conn, response_token(user, token, response_attrs))
json(conn, Token.Response.build(user, token, response_attrs))
else
_error ->
put_status(conn, 400)
@ -179,14 +177,14 @@ defmodule Pleroma.Web.OAuth.OAuthController do
conn,
%{"grant_type" => "password"} = params
) do
with {_, {:ok, %User{} = user}} <- {:get_user, Authenticator.get_user(conn)},
%App{} = app <- get_app_from_request(conn, params),
with {:ok, %User{} = user} <- Authenticator.get_user(conn),
{:ok, app} <- Token.Utils.fetch_app(conn),
{:auth_active, true} <- {:auth_active, User.auth_active?(user)},
{:user_active, true} <- {:user_active, !user.info.deactivated},
{:ok, scopes} <- validate_scopes(app, params),
{:ok, auth} <- Authorization.create_authorization(app, user, scopes),
{:ok, token} <- Token.exchange_token(app, auth) do
json(conn, response_token(user, token))
json(conn, Token.Response.build(user, token))
else
{:auth_active, false} ->
# Per https://github.com/tootsuite/mastodon/blob/
@ -218,11 +216,23 @@ defmodule Pleroma.Web.OAuth.OAuthController do
token_exchange(conn, params)
end
def token_exchange(conn, %{"grant_type" => "client_credentials"} = _params) do
with {:ok, app} <- Token.Utils.fetch_app(conn),
{:ok, auth} <- Authorization.create_authorization(app, %User{}),
{:ok, token} <- Token.exchange_token(app, auth) do
json(conn, Token.Response.build_for_client_credentials(token))
else
_error ->
put_status(conn, 400)
|> json(%{error: "Invalid credentials"})
end
end
# Bad request
def token_exchange(conn, params), do: bad_request(conn, params)
def token_revoke(conn, %{"token" => _token} = params) do
with %App{} = app <- get_app_from_request(conn, params),
with {:ok, app} <- Token.Utils.fetch_app(conn),
{:ok, _token} <- RevokeToken.revoke(app, params) do
json(conn, %{})
else
@ -252,7 +262,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
auth_attrs
|> Map.delete("scopes")
|> Map.put("scope", scope)
|> Poison.encode!()
|> Jason.encode!()
params =
auth_attrs
@ -316,7 +326,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
end
defp callback_params(%{"state" => state} = params) do
Map.merge(params, Poison.decode!(state))
Map.merge(params, Jason.decode!(state))
end
def registration_details(conn, %{"authorization" => auth_attrs}) do
@ -405,33 +415,6 @@ defmodule Pleroma.Web.OAuth.OAuthController do
end
end
defp get_app_from_request(conn, params) do
conn
|> fetch_client_credentials(params)
|> fetch_client
end
defp fetch_client({id, secret}) when is_binary(id) and is_binary(secret) do
Repo.get_by(App, client_id: id, client_secret: secret)
end
defp fetch_client({_id, _secret}), do: nil
defp fetch_client_credentials(conn, params) do
# Per RFC 6749, HTTP Basic is preferred to body params
with ["Basic " <> encoded] <- get_req_header(conn, "authorization"),
{:ok, decoded} <- Base.decode64(encoded),
[id, secret] <-
Enum.map(
String.split(decoded, ":"),
fn s -> URI.decode_www_form(s) end
) do
{id, secret}
else
_ -> {params["client_id"], params["client_secret"]}
end
end
# Special case: Local MastodonFE
defp redirect_uri(conn, "."), do: mastodon_api_url(conn, :login)
@ -442,18 +425,6 @@ defmodule Pleroma.Web.OAuth.OAuthController do
defp put_session_registration_id(conn, registration_id),
do: put_session(conn, :registration_id, registration_id)
defp response_token(%User{} = user, token, opts \\ %{}) do
%{
token_type: "Bearer",
access_token: token.token,
refresh_token: token.refresh_token,
expires_in: @expires_in,
scope: Enum.join(token.scopes, " "),
me: user.ap_id
}
|> Map.merge(opts)
end
@spec validate_scopes(App.t(), map()) ::
{:ok, list()} | {:error, :missing_scopes | :unsupported_scopes}
defp validate_scopes(app, params) do

View file

@ -45,12 +45,16 @@ defmodule Pleroma.Web.OAuth.Token do
|> Repo.find_resource()
end
@spec exchange_token(App.t(), Authorization.t()) ::
{:ok, Token.t()} | {:error, Changeset.t()}
def exchange_token(app, auth) do
with {:ok, auth} <- Authorization.use_token(auth),
true <- auth.app_id == app.id do
user = if auth.user_id, do: User.get_cached_by_id(auth.user_id), else: %User{}
create_token(
app,
User.get_cached_by_id(auth.user_id),
user,
%{scopes: auth.scopes}
)
end
@ -81,12 +85,13 @@ defmodule Pleroma.Web.OAuth.Token do
|> validate_required([:valid_until])
end
@spec create_token(App.t(), User.t(), map()) :: {:ok, Token} | {:error, Changeset.t()}
def create_token(%App{} = app, %User{} = user, attrs \\ %{}) do
%__MODULE__{user_id: user.id, app_id: app.id}
|> cast(%{scopes: attrs[:scopes] || app.scopes}, [:scopes])
|> validate_required([:scopes, :user_id, :app_id])
|> validate_required([:scopes, :app_id])
|> put_valid_until(attrs)
|> put_token
|> put_token()
|> put_refresh_token(attrs)
|> Repo.insert()
end

View file

@ -0,0 +1,32 @@
defmodule Pleroma.Web.OAuth.Token.Response do
@moduledoc false
alias Pleroma.User
alias Pleroma.Web.OAuth.Token.Utils
@expires_in Pleroma.Config.get([:oauth2, :token_expires_in], 600)
@doc false
def build(%User{} = user, token, opts \\ %{}) do
%{
token_type: "Bearer",
access_token: token.token,
refresh_token: token.refresh_token,
expires_in: @expires_in,
scope: Enum.join(token.scopes, " "),
me: user.ap_id
}
|> Map.merge(opts)
end
def build_for_client_credentials(token) do
%{
token_type: "Bearer",
access_token: token.token,
refresh_token: token.refresh_token,
created_at: Utils.format_created_at(token),
expires_in: @expires_in,
scope: Enum.join(token.scopes, " ")
}
end
end

View file

@ -3,6 +3,44 @@ defmodule Pleroma.Web.OAuth.Token.Utils do
Auxiliary functions for dealing with tokens.
"""
alias Pleroma.Repo
alias Pleroma.Web.OAuth.App
@doc "Fetch app by client credentials from request"
@spec fetch_app(Plug.Conn.t()) :: {:ok, App.t()} | {:error, :not_found}
def fetch_app(conn) do
res =
conn
|> fetch_client_credentials()
|> fetch_client
case res do
%App{} = app -> {:ok, app}
_ -> {:error, :not_found}
end
end
defp fetch_client({id, secret}) when is_binary(id) and is_binary(secret) do
Repo.get_by(App, client_id: id, client_secret: secret)
end
defp fetch_client({_id, _secret}), do: nil
defp fetch_client_credentials(conn) do
# Per RFC 6749, HTTP Basic is preferred to body params
with ["Basic " <> encoded] <- Plug.Conn.get_req_header(conn, "authorization"),
{:ok, decoded} <- Base.decode64(encoded),
[id, secret] <-
Enum.map(
String.split(decoded, ":"),
fn s -> URI.decode_www_form(s) end
) do
{id, secret}
else
_ -> {conn.params["client_id"], conn.params["client_secret"]}
end
end
@doc "convert token inserted_at to unix timestamp"
def format_created_at(%{inserted_at: inserted_at} = _token) do
inserted_at

View file

@ -18,15 +18,18 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
end
end
defp get_in_reply_to(%{"object" => %{"inReplyTo" => in_reply_to}}) do
[
{:"thr:in-reply-to",
[ref: to_charlist(in_reply_to), href: to_charlist(get_href(in_reply_to))], []}
]
defp get_in_reply_to(activity) do
with %Object{data: %{"inReplyTo" => in_reply_to}} <- Object.normalize(activity) do
[
{:"thr:in-reply-to",
[ref: to_charlist(in_reply_to), href: to_charlist(get_href(in_reply_to))], []}
]
else
_ ->
[]
end
end
defp get_in_reply_to(_), do: []
defp get_mentions(to) do
Enum.map(to, fn id ->
cond do
@ -98,7 +101,7 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
[]}
end)
in_reply_to = get_in_reply_to(activity.data)
in_reply_to = get_in_reply_to(activity)
author = if with_author, do: [{:author, UserRepresenter.to_simple_form(user)}], else: []
mentions = activity.recipients |> get_mentions
@ -146,7 +149,6 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
updated_at = activity.data["published"]
inserted_at = activity.data["published"]
_in_reply_to = get_in_reply_to(activity.data)
author = if with_author, do: [{:author, UserRepresenter.to_simple_form(user)}], else: []
mentions = activity.recipients |> get_mentions
@ -177,7 +179,6 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
updated_at = activity.data["published"]
inserted_at = activity.data["published"]
_in_reply_to = get_in_reply_to(activity.data)
author = if with_author, do: [{:author, UserRepresenter.to_simple_form(user)}], else: []
retweeted_activity = Activity.get_create_by_object_ap_id(activity.data["object"])

View file

@ -16,6 +16,7 @@ defmodule Pleroma.Web.OStatus do
alias Pleroma.Web
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.OStatus.DeleteHandler
alias Pleroma.Web.OStatus.FollowHandler
alias Pleroma.Web.OStatus.NoteHandler
@ -30,7 +31,7 @@ defmodule Pleroma.Web.OStatus do
is_nil(object) ->
false
object.data["type"] == "Note" ->
Visibility.is_public?(activity) && object.data["type"] == "Note" ->
true
true ->

View file

@ -34,4 +34,6 @@ defmodule Pleroma.Web.RichMedia.Helpers do
end
def fetch_data_for_activity(_), do: %{}
def perform(:fetch, %Activity{} = activity), do: fetch_data_for_activity(activity)
end

View file

@ -84,11 +84,13 @@ defmodule Pleroma.Web.Router do
plug(Pleroma.Plugs.EnsureUserKeyPlug)
end
pipeline :oauth_read_or_unauthenticated do
pipeline :oauth_read_or_public do
plug(Pleroma.Plugs.OAuthScopesPlug, %{
scopes: ["read"],
fallback: :proceed_unauthenticated
})
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
end
pipeline :oauth_read do
@ -146,34 +148,52 @@ defmodule Pleroma.Web.Router do
scope "/api/pleroma/admin", Pleroma.Web.AdminAPI do
pipe_through([:admin_api, :oauth_write])
post("/user/follow", AdminAPIController, :user_follow)
post("/user/unfollow", AdminAPIController, :user_unfollow)
get("/users", AdminAPIController, :list_users)
get("/users/:nickname", AdminAPIController, :user_show)
post("/users/follow", AdminAPIController, :user_follow)
post("/users/unfollow", AdminAPIController, :user_unfollow)
# TODO: to be removed at version 1.0
delete("/user", AdminAPIController, :user_delete)
patch("/users/:nickname/toggle_activation", AdminAPIController, :user_toggle_activation)
post("/user", AdminAPIController, :user_create)
delete("/users", AdminAPIController, :user_delete)
post("/users", AdminAPIController, :user_create)
patch("/users/:nickname/toggle_activation", AdminAPIController, :user_toggle_activation)
put("/users/tag", AdminAPIController, :tag_users)
delete("/users/tag", AdminAPIController, :untag_users)
# TODO: to be removed at version 1.0
get("/permission_group/:nickname", AdminAPIController, :right_get)
get("/permission_group/:nickname/:permission_group", AdminAPIController, :right_get)
post("/permission_group/:nickname/:permission_group", AdminAPIController, :right_add)
delete("/permission_group/:nickname/:permission_group", AdminAPIController, :right_delete)
put("/activation_status/:nickname", AdminAPIController, :set_activation_status)
get("/users/:nickname/permission_group", AdminAPIController, :right_get)
get("/users/:nickname/permission_group/:permission_group", AdminAPIController, :right_get)
post("/users/:nickname/permission_group/:permission_group", AdminAPIController, :right_add)
delete(
"/users/:nickname/permission_group/:permission_group",
AdminAPIController,
:right_delete
)
put("/users/:nickname/activation_status", AdminAPIController, :set_activation_status)
post("/relay", AdminAPIController, :relay_follow)
delete("/relay", AdminAPIController, :relay_unfollow)
get("/invite_token", AdminAPIController, :get_invite_token)
get("/invites", AdminAPIController, :invites)
post("/revoke_invite", AdminAPIController, :revoke_invite)
post("/email_invite", AdminAPIController, :email_invite)
get("/users/invite_token", AdminAPIController, :get_invite_token)
get("/users/invites", AdminAPIController, :invites)
post("/users/revoke_invite", AdminAPIController, :revoke_invite)
post("/users/email_invite", AdminAPIController, :email_invite)
# TODO: to be removed at version 1.0
get("/password_reset", AdminAPIController, :get_password_reset)
get("/users/:nickname/password_reset", AdminAPIController, :get_password_reset)
get("/users", AdminAPIController, :list_users)
get("/users/:nickname", AdminAPIController, :user_show)
end
scope "/", Pleroma.Web.TwitterAPI do
@ -197,6 +217,7 @@ defmodule Pleroma.Web.Router do
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
@ -367,6 +388,8 @@ defmodule Pleroma.Web.Router do
scope "/api/v1", Pleroma.Web.MastodonAPI do
pipe_through(:api)
post("/accounts", MastodonAPIController, :account_register)
get("/instance", MastodonAPIController, :masto_instance)
get("/instance/peers", MastodonAPIController, :peers)
post("/apps", MastodonAPIController, :create_app)
@ -383,7 +406,7 @@ defmodule Pleroma.Web.Router do
get("/accounts/search", MastodonAPIController, :account_search)
scope [] do
pipe_through(:oauth_read_or_unauthenticated)
pipe_through(:oauth_read_or_public)
get("/timelines/public", MastodonAPIController, :public_timeline)
get("/timelines/tag/:tag", MastodonAPIController, :hashtag_timeline)
@ -404,7 +427,7 @@ defmodule Pleroma.Web.Router do
end
scope "/api/v2", Pleroma.Web.MastodonAPI do
pipe_through([:api, :oauth_read_or_unauthenticated])
pipe_through([:api, :oauth_read_or_public])
get("/search", MastodonAPIController, :search2)
end
@ -434,7 +457,7 @@ defmodule Pleroma.Web.Router do
)
scope [] do
pipe_through(:oauth_read_or_unauthenticated)
pipe_through(:oauth_read_or_public)
get("/statuses/user_timeline", TwitterAPI.Controller, :user_timeline)
get("/qvitter/statuses/user_timeline", TwitterAPI.Controller, :user_timeline)
@ -452,7 +475,7 @@ defmodule Pleroma.Web.Router do
end
scope "/api", Pleroma.Web do
pipe_through([:api, :oauth_read_or_unauthenticated])
pipe_through([:api, :oauth_read_or_public])
get("/statuses/public_timeline", TwitterAPI.Controller, :public_timeline)
@ -466,7 +489,7 @@ defmodule Pleroma.Web.Router do
end
scope "/api", Pleroma.Web, as: :twitter_api_search do
pipe_through([:api, :oauth_read_or_unauthenticated])
pipe_through([:api, :oauth_read_or_public])
get("/pleroma/search_user", TwitterAPI.Controller, :search_user)
end
@ -650,7 +673,7 @@ defmodule Pleroma.Web.Router do
delete("/auth/sign_out", MastodonAPIController, :logout)
scope [] do
pipe_through(:oauth_read_or_unauthenticated)
pipe_through(:oauth_read_or_public)
get("/web/*path", MastodonAPIController, :index)
end
end

View file

@ -3,12 +3,18 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Salmon do
@behaviour Pleroma.Web.Federator.Publisher
@httpoison Application.get_env(:pleroma, :httpoison)
use Bitwise
alias Pleroma.Activity
alias Pleroma.Instances
alias Pleroma.User
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.Federator.Publisher
alias Pleroma.Web.OStatus
alias Pleroma.Web.OStatus.ActivityRepresenter
alias Pleroma.Web.XML
@ -165,12 +171,12 @@ defmodule Pleroma.Web.Salmon do
end
@doc "Pushes an activity to remote account."
def send_to_user(%{recipient: %{info: %{salmon: salmon}}} = params),
do: send_to_user(Map.put(params, :recipient, salmon))
def publish_one(%{recipient: %{info: %{salmon: salmon}}} = params),
do: publish_one(Map.put(params, :recipient, salmon))
def send_to_user(%{recipient: url, feed: feed, poster: poster} = params) when is_binary(url) do
def publish_one(%{recipient: url, feed: feed} = params) when is_binary(url) do
with {:ok, %{status: code}} when code in 200..299 <-
poster.(
@httpoison.post(
url,
feed,
[{"Content-Type", "application/magic-envelope+xml"}]
@ -184,11 +190,11 @@ defmodule Pleroma.Web.Salmon do
e ->
unless params[:unreachable_since], do: Instances.set_reachable(url)
Logger.debug(fn -> "Pushing Salmon to #{url} failed, #{inspect(e)}" end)
:error
{:error, "Unreachable instance"}
end
end
def send_to_user(_), do: :noop
def publish_one(_), do: :noop
@supported_activities [
"Create",
@ -199,13 +205,19 @@ defmodule Pleroma.Web.Salmon do
"Delete"
]
def is_representable?(%Activity{data: %{"type" => type}} = activity)
when type in @supported_activities,
do: Visibility.is_public?(activity)
def is_representable?(_), do: false
@doc """
Publishes an activity to remote accounts
"""
@spec publish(User.t(), Pleroma.Activity.t(), Pleroma.HTTP.t()) :: none
def publish(user, activity, poster \\ &@httpoison.post/3)
@spec publish(User.t(), Pleroma.Activity.t()) :: none
def publish(user, activity)
def publish(%{info: %{keys: keys}} = user, %{data: %{"type" => type}} = activity, poster)
def publish(%{info: %{keys: keys}} = user, %{data: %{"type" => type}} = activity)
when type in @supported_activities do
feed = ActivityRepresenter.to_simple_form(activity, user, true)
@ -229,15 +241,29 @@ defmodule Pleroma.Web.Salmon do
|> Enum.each(fn remote_user ->
Logger.debug(fn -> "Sending Salmon to #{remote_user.ap_id}" end)
Pleroma.Web.Federator.publish_single_salmon(%{
Publisher.enqueue_one(__MODULE__, %{
recipient: remote_user,
feed: feed,
poster: poster,
unreachable_since: reachable_urls_metadata[remote_user.info.salmon]
})
end)
end
end
def publish(%{id: id}, _, _), do: Logger.debug(fn -> "Keys missing for user #{id}" end)
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_from_pem(user.info.keys)
magic_key = encode_key(public)
[
%{"rel" => "salmon", "href" => OStatus.salmon_path(user)},
%{
"rel" => "magic-public-key",
"href" => "data:application/magic-public-key,#{magic_key}"
}
]
end
def gather_nodeinfo_protocol_names, do: []
end

View file

@ -173,8 +173,6 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
def config(conn, _params) do
instance = Pleroma.Config.get(:instance)
instance_fe = Pleroma.Config.get(:fe)
instance_chat = Pleroma.Config.get(:chat)
case get_format(conn) do
"xml" ->
@ -219,31 +217,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
if(Pleroma.Config.get([:instance, :safe_dm_mentions]), do: "1", else: "0")
}
pleroma_fe =
if instance_fe do
%{
theme: Keyword.get(instance_fe, :theme),
background: Keyword.get(instance_fe, :background),
logo: Keyword.get(instance_fe, :logo),
logoMask: Keyword.get(instance_fe, :logo_mask),
logoMargin: Keyword.get(instance_fe, :logo_margin),
redirectRootNoLogin: Keyword.get(instance_fe, :redirect_root_no_login),
redirectRootLogin: Keyword.get(instance_fe, :redirect_root_login),
chatDisabled: !Keyword.get(instance_chat, :enabled),
showInstanceSpecificPanel: Keyword.get(instance_fe, :show_instance_panel),
scopeOptionsEnabled: Keyword.get(instance_fe, :scope_options_enabled),
formattingOptionsEnabled: Keyword.get(instance_fe, :formatting_options_enabled),
collapseMessageWithSubject:
Keyword.get(instance_fe, :collapse_message_with_subject),
hidePostStats: Keyword.get(instance_fe, :hide_post_stats),
hideUserStats: Keyword.get(instance_fe, :hide_user_stats),
scopeCopy: Keyword.get(instance_fe, :scope_copy),
subjectLineBehavior: Keyword.get(instance_fe, :subject_line_behavior),
alwaysShowSubjectInput: Keyword.get(instance_fe, :always_show_subject_input)
}
else
Pleroma.Config.get([:frontend_configurations, :pleroma_fe])
end
pleroma_fe = Pleroma.Config.get([:frontend_configurations, :pleroma_fe])
managed_config = Keyword.get(instance, :managed_config)
@ -309,8 +283,13 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
Enum.map(lines, fn line ->
String.split(line, ",") |> List.first()
end)
|> List.delete("Account address"),
{:ok, _} = Task.start(fn -> User.follow_import(follower, followed_identifiers) end) do
|> List.delete("Account address") do
PleromaJobQueue.enqueue(:background, User, [
:follow_import,
follower,
followed_identifiers
])
json(conn, "job started")
end
end
@ -320,8 +299,13 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
end
def blocks_import(%{assigns: %{user: blocker}} = conn, %{"list" => list}) do
with blocked_identifiers <- String.split(list),
{:ok, _} = Task.start(fn -> User.blocks_import(blocker, blocked_identifiers) end) do
with blocked_identifiers <- String.split(list) do
PleromaJobQueue.enqueue(:background, User, [
:blocks_import,
blocker,
blocked_identifiers
])
json(conn, "job started")
end
end
@ -360,6 +344,17 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
end
end
def disable_account(%{assigns: %{user: user}} = conn, params) do
case CommonAPI.Utils.confirm_current_password(user, params["password"]) do
{:ok, user} ->
User.deactivate_async(user)
json(conn, %{status: "success"})
{:error, msg} ->
json(conn, %{error: msg})
end
end
def captcha(conn, _params) do
json(conn, Pleroma.Captcha.new())
end

View file

@ -128,7 +128,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
end
end
def register_user(params) do
def register_user(params, opts \\ []) do
token = params["token"]
params = %{
@ -162,13 +162,22 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
# I have no idea how this error handling works
{:error, %{error: Jason.encode!(%{captcha: [error]})}}
else
registrations_open = Pleroma.Config.get([:instance, :registrations_open])
registration_process(registrations_open, params, token)
registration_process(
params,
%{
registrations_open: Pleroma.Config.get([:instance, :registrations_open]),
token: token
},
opts
)
end
end
defp registration_process(registration_open, params, token)
when registration_open == false or is_nil(registration_open) do
defp registration_process(params, %{registrations_open: true}, opts) do
create_user(params, opts)
end
defp registration_process(params, %{token: token}, opts) do
invite =
unless is_nil(token) do
Repo.get_by(UserInviteToken, %{token: token})
@ -182,19 +191,15 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
invite when valid_invite? ->
UserInviteToken.update_usage!(invite)
create_user(params)
create_user(params, opts)
_ ->
{:error, "Expired token"}
end
end
defp registration_process(true, params, _token) do
create_user(params)
end
defp create_user(params) do
changeset = User.register_changeset(%User{}, params)
defp create_user(params, opts) do
changeset = User.register_changeset(%User{}, params, opts)
case User.register(changeset) do
{:ok, user} ->
@ -231,12 +236,15 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
def get_user(user \\ nil, params) do
case params do
%{"user_id" => user_id} ->
case target = User.get_cached_by_nickname_or_id(user_id) do
case User.get_cached_by_nickname_or_id(user_id) do
nil ->
{:error, "No user with such user_id"}
_ ->
{:ok, target}
%User{info: %{deactivated: true}} ->
{:error, "User has been disabled"}
user ->
{:ok, user}
end
%{"screen_name" => nickname} ->

View file

@ -440,7 +440,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
true <- user.local,
true <- user.info.confirmation_pending,
true <- user.info.confirmation_token == token,
info_change <- User.Info.confirmation_changeset(user.info, :confirmed),
info_change <- User.Info.confirmation_changeset(user.info, need_confirmation: false),
changeset <- Changeset.change(user) |> Changeset.put_embed(:info, info_change),
{:ok, _} <- User.update_and_set_cache(changeset) do
conn

View file

@ -170,7 +170,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do
created_at = activity.data["published"] |> Utils.date_to_asctime()
announced_activity = Activity.get_create_by_object_ap_id(activity.data["object"])
text = "#{user.nickname} retweeted a status."
text = "#{user.nickname} repeated a status."
retweeted_status = render("activity.json", Map.merge(opts, %{activity: announced_activity}))
@ -310,7 +310,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do
"tags" => tags,
"activity_type" => "post",
"possibly_sensitive" => possibly_sensitive,
"visibility" => StatusView.get_visibility(object),
"visibility" => Pleroma.Web.ActivityPub.Visibility.get_visibility(object),
"summary" => summary,
"summary_html" => summary |> Formatter.emojify(object.data["emoji"]),
"card" => card,

View file

@ -7,7 +7,7 @@ defmodule Pleroma.Web.WebFinger do
alias Pleroma.User
alias Pleroma.Web
alias Pleroma.Web.OStatus
alias Pleroma.Web.Federator.Publisher
alias Pleroma.Web.Salmon
alias Pleroma.Web.XML
alias Pleroma.XmlBuilder
@ -50,70 +50,40 @@ defmodule Pleroma.Web.WebFinger do
end
end
defp gather_links(%User{} = user) do
[
%{
"rel" => "http://webfinger.net/rel/profile-page",
"type" => "text/html",
"href" => user.ap_id
}
] ++ Publisher.gather_webfinger_links(user)
end
def represent_user(user, "JSON") do
{:ok, user} = ensure_keys_present(user)
{:ok, _private, public} = Salmon.keys_from_pem(user.info.keys)
magic_key = Salmon.encode_key(public)
%{
"subject" => "acct:#{user.nickname}@#{Pleroma.Web.Endpoint.host()}",
"aliases" => [user.ap_id],
"links" => [
%{
"rel" => "http://schemas.google.com/g/2010#updates-from",
"type" => "application/atom+xml",
"href" => OStatus.feed_path(user)
},
%{
"rel" => "http://webfinger.net/rel/profile-page",
"type" => "text/html",
"href" => user.ap_id
},
%{"rel" => "salmon", "href" => OStatus.salmon_path(user)},
%{
"rel" => "magic-public-key",
"href" => "data:application/magic-public-key,#{magic_key}"
},
%{"rel" => "self", "type" => "application/activity+json", "href" => user.ap_id},
%{
"rel" => "self",
"type" => "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"",
"href" => user.ap_id
},
%{
"rel" => "http://ostatus.org/schema/1.0/subscribe",
"template" => OStatus.remote_follow_path()
}
]
"links" => gather_links(user)
}
end
def represent_user(user, "XML") do
{:ok, user} = ensure_keys_present(user)
{:ok, _private, public} = Salmon.keys_from_pem(user.info.keys)
magic_key = Salmon.encode_key(public)
links =
gather_links(user)
|> Enum.map(fn link -> {:Link, link} end)
{
:XRD,
%{xmlns: "http://docs.oasis-open.org/ns/xri/xrd-1.0"},
[
{:Subject, "acct:#{user.nickname}@#{Pleroma.Web.Endpoint.host()}"},
{:Alias, user.ap_id},
{:Link,
%{
rel: "http://schemas.google.com/g/2010#updates-from",
type: "application/atom+xml",
href: OStatus.feed_path(user)
}},
{:Link,
%{rel: "http://webfinger.net/rel/profile-page", type: "text/html", href: user.ap_id}},
{:Link, %{rel: "salmon", href: OStatus.salmon_path(user)}},
{:Link,
%{rel: "magic-public-key", href: "data:application/magic-public-key,#{magic_key}"}},
{:Link, %{rel: "self", type: "application/activity+json", href: user.ap_id}},
{:Link,
%{rel: "http://ostatus.org/schema/1.0/subscribe", template: OStatus.remote_follow_path()}}
]
{:Alias, user.ap_id}
] ++ links
}
|> XmlBuilder.to_doc()
end

View file

@ -4,10 +4,14 @@
defmodule Pleroma.Web.Websub do
alias Ecto.Changeset
alias Pleroma.Activity
alias Pleroma.Instances
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.Endpoint
alias Pleroma.Web.Federator
alias Pleroma.Web.Federator.Publisher
alias Pleroma.Web.OStatus
alias Pleroma.Web.OStatus.FeedRepresenter
alias Pleroma.Web.Router.Helpers
@ -18,6 +22,8 @@ defmodule Pleroma.Web.Websub do
import Ecto.Query
@behaviour Pleroma.Web.Federator.Publisher
@httpoison Application.get_env(:pleroma, :httpoison)
def verify(subscription, getter \\ &@httpoison.get/3) do
@ -56,6 +62,13 @@ defmodule Pleroma.Web.Websub do
"Undo",
"Delete"
]
def is_representable?(%Activity{data: %{"type" => type}} = activity)
when type in @supported_activities,
do: Visibility.is_public?(activity)
def is_representable?(_), do: false
def publish(topic, user, %{data: %{"type" => type}} = activity)
when type in @supported_activities do
response =
@ -88,12 +101,14 @@ defmodule Pleroma.Web.Websub do
unreachable_since: reachable_callbacks_metadata[sub.callback]
}
Federator.publish_single_websub(data)
Publisher.enqueue_one(__MODULE__, data)
end)
end
def publish(_, _, _), do: ""
def publish(actor, activity), do: publish(Pleroma.Web.OStatus.feed_path(actor), actor, activity)
def sign(secret, doc) do
:crypto.hmac(:sha, secret, to_string(doc)) |> Base.encode16() |> String.downcase()
end
@ -299,4 +314,20 @@ defmodule Pleroma.Web.Websub do
{:error, response}
end
end
def gather_webfinger_links(%User{} = user) do
[
%{
"rel" => "http://schemas.google.com/g/2010#updates-from",
"type" => "application/atom+xml",
"href" => OStatus.feed_path(user)
},
%{
"rel" => "http://ostatus.org/schema/1.0/subscribe",
"template" => OStatus.remote_follow_path()
}
]
end
def gather_nodeinfo_protocol_names, do: ["ostatus"]
end

View file

@ -35,6 +35,7 @@ defmodule Pleroma.XmlBuilder do
defp make_open_tag(tag, attributes) do
attributes_string =
for {attribute, value} <- attributes do
value = String.replace(value, "\"", "&quot;")
"#{attribute}=\"#{value}\""
end
|> Enum.join(" ")